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,19 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.microsoftExcel",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Data & Storage", "Productivity"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/microsoft/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.microsoftexcel/"
|
||||
}
|
||||
]
|
||||
},
|
||||
"alias": ["_Excel", "Excel", "Sheet", "CSV", "Spreadsheet"]
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import type { INodeTypeBaseDescription, IVersionedNodeType } from 'n8n-workflow';
|
||||
import { VersionedNodeType } from 'n8n-workflow';
|
||||
|
||||
import { MicrosoftExcelV1 } from './v1/MicrosoftExcelV1.node';
|
||||
import { MicrosoftExcelV2 } from './v2/MicrosoftExcelV2.node';
|
||||
|
||||
export class MicrosoftExcel extends VersionedNodeType {
|
||||
constructor() {
|
||||
const baseDescription: INodeTypeBaseDescription = {
|
||||
displayName: 'Microsoft Excel 365',
|
||||
name: 'microsoftExcel',
|
||||
icon: 'file:excel.svg',
|
||||
group: ['input'],
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Microsoft Excel API',
|
||||
defaultVersion: 2.2,
|
||||
};
|
||||
|
||||
const nodeVersions: IVersionedNodeType['nodeVersions'] = {
|
||||
1: new MicrosoftExcelV1(baseDescription),
|
||||
2: new MicrosoftExcelV2(baseDescription),
|
||||
2.1: new MicrosoftExcelV2(baseDescription),
|
||||
2.2: new MicrosoftExcelV2(baseDescription),
|
||||
};
|
||||
|
||||
super(nodeVersions, baseDescription);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60"><g fill="#02723B" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"><path d="M43.1 20.227h9.512v4.692h-9.513zm0-7.034h9.512v4.69h-9.513zm0 14.227h9.512v4.691h-9.513zm0 7.036h9.512v4.691h-9.513z"/><path d="M43.1 41.649h9.512v4.69h-9.513zM58.07 6.78H35.146V.37L.37 6.467v47.22l34.776 5.942v-7.186H58.07c.935 0 1.559-.781 1.559-1.555V8.344c0-.781-.624-1.555-1.56-1.555V6.78zM20.018 40.554l-3.274-8.593h-.148c0 .149-3.275 8.13-3.275 8.13l-4.366-.312L14.1 29.616 9.422 19.453l4.367-.312 2.963 7.974h.148c0-.148 3.274-8.287 3.274-8.287l4.522-.312-5.457 11.101 5.614 11.102-4.834-.149zM57.6 50.717H35.146v-4.37h5.458v-4.691h-5.458v-2.352h5.458v-4.69h-5.458v-2.347h5.458v-4.69h-5.458V25.23h5.458v-4.69h-5.458v-2.345h5.458v-4.692h-5.458v-4.69h22.456v41.903h-.001z"/><path d="M42.729 19.857h9.512v4.691H42.73zm0-7.035h9.512v4.691H42.73zm0 14.228h9.512v4.69H42.73zm0 7.036h9.512v4.691H42.73z"/><path d="M42.729 41.279h9.512v4.69H42.73zM57.699 6.41H34.776V0L0 6.098v47.22l34.776 5.941v-7.185h22.923c.936 0 1.56-.781 1.56-1.555V7.974c0-.781-.624-1.555-1.56-1.555zM19.647 40.184l-3.274-8.593h-.148c0 .148-3.274 8.13-3.274 8.13l-4.367-.312 5.146-10.163-4.678-10.163 4.367-.313 2.962 7.974h.149c0-.148 3.274-8.286 3.274-8.286l4.522-.313-5.458 11.102 5.615 11.101-4.834-.148zM57.23 50.347H34.776v-4.37h5.457v-4.692h-5.457v-2.352h5.457v-4.69h-5.457v-2.347h5.457v-4.69h-5.457V24.86h5.457v-4.69h-5.457v-2.346h5.457v-4.692h-5.457v-4.69h22.456v41.904z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,8 @@
|
||||
export const credentials = {
|
||||
microsoftExcelOAuth2Api: {
|
||||
scope: 'openid',
|
||||
oauthTokenData: {
|
||||
access_token: 'token',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { ILoadOptionsFunctions } from 'n8n-workflow';
|
||||
|
||||
import { getWorksheetColumnRow } from '../../../v2/methods/loadOptions';
|
||||
import { microsoftApiRequest } from '../../../v2/transport';
|
||||
|
||||
// Mock the transport module
|
||||
jest.mock('../../../v2/transport', () => ({
|
||||
microsoftApiRequest: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Microsoft Excel V2 - loadOptions', () => {
|
||||
let mockContext: ILoadOptionsFunctions;
|
||||
const mockMicrosoftApiRequest = microsoftApiRequest as jest.MockedFunction<
|
||||
typeof microsoftApiRequest
|
||||
>;
|
||||
const mockGetNodeParameter = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
mockContext = mock<ILoadOptionsFunctions>({
|
||||
getNodeParameter: mockGetNodeParameter,
|
||||
});
|
||||
mockMicrosoftApiRequest.mockReset();
|
||||
mockGetNodeParameter.mockReset();
|
||||
});
|
||||
|
||||
describe('getWorksheetColumnRow', () => {
|
||||
it('should get columns from usedRange when range is empty', async () => {
|
||||
// Arrange
|
||||
const workbookId = 'test-workbook-id';
|
||||
const worksheetId = 'test-worksheet-id';
|
||||
const range = '';
|
||||
|
||||
mockGetNodeParameter
|
||||
.mockReturnValueOnce(workbookId)
|
||||
.mockReturnValueOnce(worksheetId)
|
||||
.mockReturnValueOnce(range);
|
||||
|
||||
const mockResponse = {
|
||||
values: [['Column A', 'Column B', 'Column C']],
|
||||
};
|
||||
|
||||
mockMicrosoftApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await getWorksheetColumnRow.call(mockContext);
|
||||
|
||||
expect(mockMicrosoftApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
|
||||
undefined,
|
||||
{ select: 'values' },
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ name: 'Column A', value: 'Column A' },
|
||||
{ name: 'Column B', value: 'Column B' },
|
||||
{ name: 'Column C', value: 'Column C' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should get columns from specific range when range is provided', async () => {
|
||||
const workbookId = 'test-workbook-id';
|
||||
const worksheetId = 'test-worksheet-id';
|
||||
const range = 'A1:C1';
|
||||
|
||||
mockGetNodeParameter
|
||||
.mockReturnValueOnce(workbookId)
|
||||
.mockReturnValueOnce(worksheetId)
|
||||
.mockReturnValueOnce(range);
|
||||
|
||||
const mockResponse = {
|
||||
values: [['Header 1', 'Header 2', 'Header 3']],
|
||||
};
|
||||
|
||||
mockMicrosoftApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await getWorksheetColumnRow.call(mockContext);
|
||||
|
||||
expect(mockMicrosoftApiRequest).toHaveBeenCalledWith(
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{ select: 'values' },
|
||||
);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ name: 'Header 1', value: 'Header 1' },
|
||||
{ name: 'Header 2', value: 'Header 2' },
|
||||
{ name: 'Header 3', value: 'Header 3' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle empty columns array', async () => {
|
||||
const workbookId = 'test-workbook-id';
|
||||
const worksheetId = 'test-worksheet-id';
|
||||
const range = '';
|
||||
|
||||
mockGetNodeParameter
|
||||
.mockReturnValueOnce(workbookId)
|
||||
.mockReturnValueOnce(worksheetId)
|
||||
.mockReturnValueOnce(range);
|
||||
|
||||
const mockResponse = {
|
||||
values: [[]],
|
||||
};
|
||||
|
||||
mockMicrosoftApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await getWorksheetColumnRow.call(mockContext);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle single column', async () => {
|
||||
const workbookId = 'test-workbook-id';
|
||||
const worksheetId = 'test-worksheet-id';
|
||||
const range = 'A1:A1';
|
||||
|
||||
mockGetNodeParameter
|
||||
.mockReturnValueOnce(workbookId)
|
||||
.mockReturnValueOnce(worksheetId)
|
||||
.mockReturnValueOnce(range);
|
||||
|
||||
const mockResponse = {
|
||||
values: [['Single Column']],
|
||||
};
|
||||
|
||||
mockMicrosoftApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await getWorksheetColumnRow.call(mockContext);
|
||||
|
||||
expect(result).toEqual([{ name: 'Single Column', value: 'Single Column' }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, table => addTable', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.post(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/tables/add',
|
||||
{ address: 'A1:D4', hasHeaders: true },
|
||||
)
|
||||
.reply(200, {
|
||||
style: 'TableStyleMedium2',
|
||||
name: 'Table3',
|
||||
showFilterButton: true,
|
||||
id: '{317CA469-7D1C-4A5D-9B0B-424444BF0336}',
|
||||
highlightLastColumn: false,
|
||||
highlightFirstColumn: false,
|
||||
legacyId: '3',
|
||||
showBandedColumns: false,
|
||||
showBandedRows: true,
|
||||
showHeaders: true,
|
||||
showTotals: false,
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['addTable.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "875e8784-eb59-40d8-ba45-129a5e29881c",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [380, 140]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "table",
|
||||
"operation": "addTable",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "{A0883CFE-D27E-4ECC-B94B-981830AAD55B}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Sheet4"
|
||||
},
|
||||
"selectRange": "manual",
|
||||
"range": "A1:D4"
|
||||
},
|
||||
"id": "0e0ac1d2-242c-486a-9287-c70307645acc",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [860, 140],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"style": "TableStyleMedium2",
|
||||
"name": "Table3",
|
||||
"showFilterButton": true,
|
||||
"id": "{317CA469-7D1C-4A5D-9B0B-424444BF0336}",
|
||||
"highlightLastColumn": false,
|
||||
"highlightFirstColumn": false,
|
||||
"legacyId": "3",
|
||||
"showBandedColumns": false,
|
||||
"showBandedRows": true,
|
||||
"showHeaders": true,
|
||||
"showTotals": false
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, table => append', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/tables/%7B317CA469-7D1C-4A5D-9B0B-424444BF0336%7D/columns',
|
||||
)
|
||||
.reply(200, {
|
||||
value: [{ name: 'id' }, { name: 'name' }, { name: 'age' }, { name: 'data' }],
|
||||
})
|
||||
.post(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/tables/%7B317CA469-7D1C-4A5D-9B0B-424444BF0336%7D/rows/add',
|
||||
{ values: [['3', 'Donald', '99', 'data 5']] },
|
||||
)
|
||||
.reply(200, {
|
||||
index: 3,
|
||||
values: [[3, 'Donald', 99, 'data 5']],
|
||||
})
|
||||
.post('/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/createSession')
|
||||
.reply(200, { id: 12345 })
|
||||
.post('/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/closeSession')
|
||||
.reply(200);
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['append.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "875e8784-eb59-40d8-ba45-129a5e29881c",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [380, 140]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "table",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія",
|
||||
"cachedResultUrl": "https://5w1hb7-my.sharepoint.com/personal/michaeldevsandbox_5w1hb7_onmicrosoft_com/_layouts/15/Doc.aspx?sourcedoc=%7BECC4041C-3AB6-4CF7-B079-0926470A1388%7D&file=%D0%9F%D0%A0%D0%A0%D0%9E%20%D0%BA%D0%BE%D0%BF%D1%96%D1%8F.xlsx&action=default&mobileredirect=true&DefaultItemOpen=1"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "{A0883CFE-D27E-4ECC-B94B-981830AAD55B}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Sheet4",
|
||||
"cachedResultUrl": "https://5w1hb7-my.sharepoint.com/personal/michaeldevsandbox_5w1hb7_onmicrosoft_com/_layouts/15/Doc.aspx?sourcedoc=%7BECC4041C-3AB6-4CF7-B079-0926470A1388%7D&file=%D0%9F%D0%A0%D0%A0%D0%9E%20%D0%BA%D0%BE%D0%BF%D1%96%D1%8F.xlsx&action=default&mobileredirect=true&DefaultItemOpen=1&activeCell=Sheet4!A1"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "{317CA469-7D1C-4A5D-9B0B-424444BF0336}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Table3",
|
||||
"cachedResultUrl": "https://5w1hb7-my.sharepoint.com/personal/michaeldevsandbox_5w1hb7_onmicrosoft_com/_layouts/15/Doc.aspx?sourcedoc=%7BECC4041C-3AB6-4CF7-B079-0926470A1388%7D&file=%D0%9F%D0%A0%D0%A0%D0%9E%20%D0%BA%D0%BE%D0%BF%D1%96%D1%8F.xlsx&action=default&mobileredirect=true&DefaultItemOpen=1&activeCell=Sheet4!A1:D4"
|
||||
},
|
||||
"fieldsUi": {
|
||||
"values": [
|
||||
{
|
||||
"column": "id",
|
||||
"fieldValue": "3"
|
||||
},
|
||||
{
|
||||
"column": "name",
|
||||
"fieldValue": "Donald"
|
||||
},
|
||||
{
|
||||
"column": "age",
|
||||
"fieldValue": "99"
|
||||
},
|
||||
{
|
||||
"column": "data",
|
||||
"fieldValue": "data 5"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "0e0ac1d2-242c-486a-9287-c70307645acc",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [860, 140],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"id": 3,
|
||||
"name": "Donald",
|
||||
"age": 99,
|
||||
"data": "data 5"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "b9eda2d8-e1a5-4a54-aaa9-5e81adaae909",
|
||||
"id": "135",
|
||||
"meta": {
|
||||
"instanceId": "36203ea1ce3cef713fa25999bd9874ae26b9e4c2c3a90a365f2882a154d031d0"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, table => convertToRange', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.post(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/tables/%7B6321EE4A-AC21-48AD-87D9-B527637D94B3%7D/convertToRange',
|
||||
)
|
||||
.reply(200, {
|
||||
address: 'Sheet4!A1:D5',
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Sam', 34, 'data 4'],
|
||||
[3, 'Donald', 99, 'data 5'],
|
||||
],
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['convertToRange.workflow.json'],
|
||||
});
|
||||
});
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "875e8784-eb59-40d8-ba45-129a5e29881c",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [580, 140]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "table",
|
||||
"operation": "convertToRange",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "{A0883CFE-D27E-4ECC-B94B-981830AAD55B}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Sheet4"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "{6321EE4A-AC21-48AD-87D9-B527637D94B3}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Table3"
|
||||
}
|
||||
},
|
||||
"id": "0e0ac1d2-242c-486a-9287-c70307645acc",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [860, 140],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"address": "Sheet4!A1:D5",
|
||||
"values": [
|
||||
["id", "name", "age", "data"],
|
||||
[1, "Sam", 33, "data 1"],
|
||||
[2, "Jon", 44, "data 2"],
|
||||
[3, "Sam", 34, "data 4"],
|
||||
[3, "Donald", 99, "data 5"]
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, table => deleteTable', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.delete(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/tables/%7B92FBE3F5-3180-47EE-8549-40892C38DA7F%7D',
|
||||
)
|
||||
.reply(200);
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['deleteTable.workflow.json'],
|
||||
});
|
||||
});
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "875e8784-eb59-40d8-ba45-129a5e29881c",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [580, 140]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "table",
|
||||
"operation": "deleteTable",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "{A0883CFE-D27E-4ECC-B94B-981830AAD55B}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Sheet4"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "{92FBE3F5-3180-47EE-8549-40892C38DA7F}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Table3"
|
||||
}
|
||||
},
|
||||
"id": "0e0ac1d2-242c-486a-9287-c70307645acc",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [860, 140],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, table => getColumns', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B00000000-0001-0000-0000-000000000000%7D/tables/%7B613E8967-D581-44ED-81D3-82A01AA6A05C%7D/columns?%24top=100&%24skip=0',
|
||||
)
|
||||
.reply(200, {
|
||||
value: [
|
||||
{ name: 'country' },
|
||||
{ name: 'browser' },
|
||||
{ name: 'session_duration' },
|
||||
{ name: 'visits' },
|
||||
],
|
||||
})
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B00000000-0001-0000-0000-000000000000%7D/tables/%7B613E8967-D581-44ED-81D3-82A01AA6A05C%7D/columns?%24top=100&%24skip=100',
|
||||
)
|
||||
.reply(200, { value: [] });
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['getColumns.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "875e8784-eb59-40d8-ba45-129a5e29881c",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [580, 140]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "table",
|
||||
"operation": "getColumns",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "{00000000-0001-0000-0000-000000000000}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Sheet1"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "{613E8967-D581-44ED-81D3-82A01AA6A05C}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Table1"
|
||||
},
|
||||
"returnAll": true
|
||||
},
|
||||
"id": "0e0ac1d2-242c-486a-9287-c70307645acc",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [860, 140],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"name": "country"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"name": "browser"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"name": "session_duration"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"name": "visits"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, table => getRows', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B00000000-0001-0000-0000-000000000000%7D/tables/%7B613E8967-D581-44ED-81D3-82A01AA6A05C%7D/rows?%24top=2',
|
||||
)
|
||||
.reply(200, {
|
||||
value: [
|
||||
{ index: 0, values: [['uk', 'firefox', 1, 1]] },
|
||||
{ index: 1, values: [['us', 'chrome', 1, 12]] },
|
||||
],
|
||||
})
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B00000000-0001-0000-0000-000000000000%7D/tables/%7B613E8967-D581-44ED-81D3-82A01AA6A05C%7D/columns?%24select=name&%24top=100&%24skip=0',
|
||||
)
|
||||
.reply(200, {
|
||||
value: [
|
||||
{ name: 'country' },
|
||||
{ name: 'browser' },
|
||||
{ name: 'session_duration' },
|
||||
{ name: 'visits' },
|
||||
],
|
||||
})
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B00000000-0001-0000-0000-000000000000%7D/tables/%7B613E8967-D581-44ED-81D3-82A01AA6A05C%7D/columns?%24select=name&%24top=100&%24skip=100',
|
||||
)
|
||||
.reply(200, { value: [] });
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['getRows.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "875e8784-eb59-40d8-ba45-129a5e29881c",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [580, 140]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "table",
|
||||
"operation": "getRows",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "{00000000-0001-0000-0000-000000000000}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Sheet1"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "{613E8967-D581-44ED-81D3-82A01AA6A05C}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Table1"
|
||||
},
|
||||
"limit": 2,
|
||||
"filters": {}
|
||||
},
|
||||
"id": "0e0ac1d2-242c-486a-9287-c70307645acc",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [860, 140],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"country": "uk",
|
||||
"browser": "firefox",
|
||||
"session_duration": 1,
|
||||
"visits": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"country": "us",
|
||||
"browser": "chrome",
|
||||
"session_duration": 1,
|
||||
"visits": 12
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, table => lookup', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B00000000-0001-0000-0000-000000000000%7D/tables/%7B613E8967-D581-44ED-81D3-82A01AA6A05C%7D/rows?%24top=100&%24skip=0',
|
||||
)
|
||||
.reply(200, {
|
||||
value: [
|
||||
{ index: 0, values: [['uk', 'firefox', 1, 1]] },
|
||||
{ index: 1, values: [['us', 'chrome', 1, 12]] },
|
||||
{ index: 2, values: [['test', 'test', 55, 123]] },
|
||||
{ index: 3, values: [['ua', 'chrome', 1, 3]] },
|
||||
{ index: 4, values: [['ua', 'firefox', 1, 4]] },
|
||||
{ index: 5, values: [['uk', 'chrome', 1, 55]] },
|
||||
],
|
||||
})
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B00000000-0001-0000-0000-000000000000%7D/tables/%7B613E8967-D581-44ED-81D3-82A01AA6A05C%7D/rows?%24top=100&%24skip=100',
|
||||
)
|
||||
.reply(200, { value: [] })
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B00000000-0001-0000-0000-000000000000%7D/tables/%7B613E8967-D581-44ED-81D3-82A01AA6A05C%7D/columns?%24select=name&%24top=100&%24skip=0',
|
||||
)
|
||||
.reply(200, {
|
||||
value: [
|
||||
{ name: 'country' },
|
||||
{ name: 'browser' },
|
||||
{ name: 'session_duration' },
|
||||
{ name: 'visits' },
|
||||
],
|
||||
})
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B00000000-0001-0000-0000-000000000000%7D/tables/%7B613E8967-D581-44ED-81D3-82A01AA6A05C%7D/columns?%24select=name&%24top=100&%24skip=100',
|
||||
)
|
||||
.reply(200, { value: [] });
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['lookup.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "875e8784-eb59-40d8-ba45-129a5e29881c",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [580, 140]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "table",
|
||||
"operation": "lookup",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "{00000000-0001-0000-0000-000000000000}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Sheet1"
|
||||
},
|
||||
"table": {
|
||||
"__rl": true,
|
||||
"value": "{613E8967-D581-44ED-81D3-82A01AA6A05C}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Table1"
|
||||
},
|
||||
"lookupColumn": "country",
|
||||
"lookupValue": "uk",
|
||||
"options": {
|
||||
"returnAllMatches": true
|
||||
}
|
||||
},
|
||||
"id": "0e0ac1d2-242c-486a-9287-c70307645acc",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [860, 140],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"country": "uk",
|
||||
"browser": "firefox",
|
||||
"session_duration": 1,
|
||||
"visits": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"country": "uk",
|
||||
"browser": "chrome",
|
||||
"session_duration": 1,
|
||||
"visits": 55
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, workbook => addWorksheet', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.post('/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/createSession', {
|
||||
persistChanges: true,
|
||||
})
|
||||
.reply(200, { id: 12345 })
|
||||
.post('/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/add', {
|
||||
name: 'Sheet42',
|
||||
})
|
||||
.matchHeader('workbook-session-id', '12345')
|
||||
.reply(200, {
|
||||
id: '{266ADAB7-25B6-4F28-A2D1-FD5BFBD7A4F0}',
|
||||
name: 'Sheet42',
|
||||
position: 8,
|
||||
visibility: 'Visible',
|
||||
})
|
||||
.post('/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/closeSession')
|
||||
.reply(200);
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['addWorksheet.workflow.json'],
|
||||
});
|
||||
});
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "875e8784-eb59-40d8-ba45-129a5e29881c",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [380, 140]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "addWorksheet",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"additionalFields": {
|
||||
"name": "Sheet42"
|
||||
}
|
||||
},
|
||||
"id": "0e0ac1d2-242c-486a-9287-c70307645acc",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [860, 140],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"id": "{266ADAB7-25B6-4F28-A2D1-FD5BFBD7A4F0}",
|
||||
"name": "Sheet42",
|
||||
"position": 8,
|
||||
"visibility": "Visible"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, workbook => deleteWorkbook', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.delete('/drive/items/01FUWX3BXJLISGF2CFWBGYPHXFCXPXOJUK')
|
||||
.reply(200);
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['deleteWorkbook.workflow.json'],
|
||||
});
|
||||
});
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "875e8784-eb59-40d8-ba45-129a5e29881c",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [380, 140]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "deleteWorkbook",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BXJLISGF2CFWBGYPHXFCXPXOJUK",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Book"
|
||||
}
|
||||
},
|
||||
"id": "0e0ac1d2-242c-486a-9287-c70307645acc",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [860, 140],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, workbook => getAll', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.get("/drive/root/search(q='.xlsx')?%24select=name&%24top=2")
|
||||
.reply(200, {
|
||||
value: [
|
||||
{
|
||||
'@odata.type': '#microsoft.graph.driveItem',
|
||||
name: 'ПРРО копія.xlsx',
|
||||
},
|
||||
{
|
||||
'@odata.type': '#microsoft.graph.driveItem',
|
||||
name: 'Book 3.xlsx',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['getAll.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "875e8784-eb59-40d8-ba45-129a5e29881c",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [380, 140]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"limit": 2,
|
||||
"filters": {
|
||||
"fields": "name"
|
||||
}
|
||||
},
|
||||
"id": "0e0ac1d2-242c-486a-9287-c70307645acc",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [860, 140],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"@odata.type": "#microsoft.graph.driveItem",
|
||||
"name": "ПРРО копія.xlsx"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"@odata.type": "#microsoft.graph.driveItem",
|
||||
"name": "Book 3.xlsx"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, worksheet => append', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/usedRange',
|
||||
)
|
||||
.reply(200, {
|
||||
address: 'Sheet4!A1:D6',
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
],
|
||||
})
|
||||
.patch(
|
||||
"/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/range(address='A7:D7')",
|
||||
)
|
||||
.reply(200, { values: [[4, 'Sam', 34, 'data 4']] })
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B426949D7-797F-43A9-A8A4-8FE283495A82%7D/usedRange',
|
||||
)
|
||||
.reply(200, {
|
||||
address: 'Sheet4!A1:D6',
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
],
|
||||
})
|
||||
.patch(
|
||||
"/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B426949D7-797F-43A9-A8A4-8FE283495A82%7D/range(address='A7:D7')",
|
||||
)
|
||||
.reply(200, { values: [[4, 'Don', 37, 'data 44']] });
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['append.workflow.json'],
|
||||
});
|
||||
});
|
||||
+171
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"name": "microsoft excel 365 - tests",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "2e1ec8f6-a2e2-4aa9-909c-d0a279584131",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [800, 260]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "worksheet",
|
||||
"operation": "append",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "={A0883CFE-D27E-4ECC-B94B-981830AAD55B}",
|
||||
"mode": "id"
|
||||
},
|
||||
"fieldsUi": {
|
||||
"values": [
|
||||
{
|
||||
"column": "id",
|
||||
"fieldValue": "4"
|
||||
},
|
||||
{
|
||||
"column": "name",
|
||||
"fieldValue": "Sam"
|
||||
},
|
||||
{
|
||||
"column": "age",
|
||||
"fieldValue": "34"
|
||||
},
|
||||
{
|
||||
"column": "data",
|
||||
"fieldValue": "data 4"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "86f2a240-3acf-45c2-b97f-63dd655d296b",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [1280, 260],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "worksheet",
|
||||
"operation": "append",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "{426949D7-797F-43A9-A8A4-8FE283495A82}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Sheet5"
|
||||
},
|
||||
"dataMode": "autoMap",
|
||||
"options": {}
|
||||
},
|
||||
"id": "531949d8-1ffa-4e1c-ae3e-032360b74f06",
|
||||
"name": "Microsoft Excel 3651",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [1280, 500],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"data": {
|
||||
"id": 4,
|
||||
"name": "Don",
|
||||
"age": 37,
|
||||
"data": "data 44"
|
||||
}
|
||||
},
|
||||
"id": "2919f9b9-e3ac-42cd-a792-774738fd2195",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-testing.testData",
|
||||
"typeVersion": 1,
|
||||
"position": [1080, 500]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 3651": [
|
||||
{
|
||||
"json": {
|
||||
"id": 4,
|
||||
"name": "Don",
|
||||
"age": 37,
|
||||
"data": "data 44"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"id": 4,
|
||||
"name": "Sam",
|
||||
"age": 34,
|
||||
"data": "data 4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Code": [
|
||||
{
|
||||
"json": {
|
||||
"id": 4,
|
||||
"name": "Don",
|
||||
"age": 37,
|
||||
"data": "data 44"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 3651",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, worksheet => clear', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.post(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BF7AF92FE-D42D-452F-8E4A-901B1D1EBF3F%7D/range/clear',
|
||||
{ applyTo: 'All' },
|
||||
)
|
||||
.reply(200, {
|
||||
values: [{ json: { success: true } }],
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['clear.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "f0857ec9-0709-4657-a2f4-059837c94060",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [540, 220]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "worksheet",
|
||||
"operation": "clear",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "{F7AF92FE-D42D-452F-8E4A-901B1D1EBF3F}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Sheet2"
|
||||
}
|
||||
},
|
||||
"id": "426ed055-0c9b-4ae2-a9fe-a6cce875d5ee",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [1020, 220],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, worksheet => deleteWorksheet', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.delete(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B88D9C37A-4180-4B23-8996-BF11F32EB63C%7D',
|
||||
)
|
||||
.reply(200, {
|
||||
values: [{ json: { success: true } }],
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['deleteWorksheet.workflow.json'],
|
||||
});
|
||||
});
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "f0857ec9-0709-4657-a2f4-059837c94060",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [540, 220]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "worksheet",
|
||||
"operation": "deleteWorksheet",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "{88D9C37A-4180-4B23-8996-BF11F32EB63C}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "188"
|
||||
}
|
||||
},
|
||||
"id": "426ed055-0c9b-4ae2-a9fe-a6cce875d5ee",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [1020, 220],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"success": true
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, worksheet => getAll', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets?%24select=name&%24top=3',
|
||||
)
|
||||
.reply(200, {
|
||||
value: [
|
||||
{
|
||||
id: '{00000000-0001-0000-0000-000000000000}',
|
||||
name: 'Sheet1',
|
||||
},
|
||||
{
|
||||
id: '{F7AF92FE-D42D-452F-8E4A-901B1D1EBF3F}',
|
||||
name: 'Sheet2',
|
||||
},
|
||||
{
|
||||
id: '{BF7BD843-4912-4B81-A0AC-4FBBC2783E20}',
|
||||
name: 'foo2',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['getAll.workflow.json'],
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "f0857ec9-0709-4657-a2f4-059837c94060",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [540, 220]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "worksheet",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"limit": 3,
|
||||
"filters": {
|
||||
"fields": "name"
|
||||
}
|
||||
},
|
||||
"id": "426ed055-0c9b-4ae2-a9fe-a6cce875d5ee",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [1020, 220],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"id": "{00000000-0001-0000-0000-000000000000}",
|
||||
"name": "Sheet1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "{F7AF92FE-D42D-452F-8E4A-901B1D1EBF3F}",
|
||||
"name": "Sheet2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": "{BF7BD843-4912-4B81-A0AC-4FBBC2783E20}",
|
||||
"name": "foo2"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, worksheet => readRows', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/usedRange',
|
||||
)
|
||||
.reply(200, {
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
],
|
||||
})
|
||||
.get(
|
||||
"/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/range(address='A1:D3')",
|
||||
)
|
||||
.reply(200, {
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
],
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['readRows.workflow.json'],
|
||||
});
|
||||
});
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
{
|
||||
"name": "microsoft excel 365 - read rows",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "2e1ec8f6-a2e2-4aa9-909c-d0a279584131",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [820, 380]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "worksheet",
|
||||
"operation": "readRows",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "{A0883CFE-D27E-4ECC-B94B-981830AAD55B}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Sheet4"
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "86f2a240-3acf-45c2-b97f-63dd655d296b",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [1100, 260],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "worksheet",
|
||||
"operation": "readRows",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія",
|
||||
"cachedResultUrl": "https://5w1hb7-my.sharepoint.com/personal/michaeldevsandbox_5w1hb7_onmicrosoft_com/_layouts/15/Doc.aspx?sourcedoc=%7BECC4041C-3AB6-4CF7-B079-0926470A1388%7D&file=%D0%9F%D0%A0%D0%A0%D0%9E%20%D0%BA%D0%BE%D0%BF%D1%96%D1%8F.xlsx&action=default&mobileredirect=true&DefaultItemOpen=1"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "{A0883CFE-D27E-4ECC-B94B-981830AAD55B}",
|
||||
"mode": "list",
|
||||
"cachedResultName": "Sheet4",
|
||||
"cachedResultUrl": "https://5w1hb7-my.sharepoint.com/personal/michaeldevsandbox_5w1hb7_onmicrosoft_com/_layouts/15/Doc.aspx?sourcedoc=%7BECC4041C-3AB6-4CF7-B079-0926470A1388%7D&file=%D0%9F%D0%A0%D0%A0%D0%9E%20%D0%BA%D0%BE%D0%BF%D1%96%D1%8F.xlsx&action=default&mobileredirect=true&DefaultItemOpen=1&activeCell=Sheet4!A1"
|
||||
},
|
||||
"useRange": true,
|
||||
"range": "A1:D3",
|
||||
"dataStartRow": 2,
|
||||
"options": {}
|
||||
},
|
||||
"id": "8ce6ab42-8f38-452b-90da-598d8a958c2b",
|
||||
"name": "Microsoft Excel 3651",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [1100, 520],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"id": 1,
|
||||
"name": "Sam",
|
||||
"age": 33,
|
||||
"data": "data 1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": 2,
|
||||
"name": "Jon",
|
||||
"age": 44,
|
||||
"data": "data 2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"json": {
|
||||
"id": 3,
|
||||
"name": "Ron",
|
||||
"age": 55,
|
||||
"data": "data 3"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Microsoft Excel 3651": [
|
||||
{
|
||||
"json": {
|
||||
"id": 2,
|
||||
"name": "Jon",
|
||||
"age": 44,
|
||||
"data": "data 2"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Microsoft Excel 3651",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, worksheet => update', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/usedRange',
|
||||
)
|
||||
.reply(200, {
|
||||
address: 'Sheet4!A1:D6',
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
],
|
||||
})
|
||||
.patch(
|
||||
"/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/range(address='A1:D6')",
|
||||
)
|
||||
.reply(200, {
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Sam', 34, 'data 4'],
|
||||
],
|
||||
})
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B426949D7-797F-43A9-A8A4-8FE283495A82%7D/usedRange',
|
||||
)
|
||||
.reply(200, {
|
||||
address: 'Sheet4!A1:D6',
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
],
|
||||
})
|
||||
.patch(
|
||||
"/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B426949D7-797F-43A9-A8A4-8FE283495A82%7D/range(address='A1:D6')",
|
||||
)
|
||||
.reply(200, {
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Don', 37, 'data 44'],
|
||||
],
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['update.workflow.json'],
|
||||
});
|
||||
});
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "875e8784-eb59-40d8-ba45-129a5e29881c",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [380, 140]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "worksheet",
|
||||
"operation": "update",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "={A0883CFE-D27E-4ECC-B94B-981830AAD55B}",
|
||||
"mode": "id"
|
||||
},
|
||||
"columnToMatchOn": "id",
|
||||
"valueToMatchOn": "3",
|
||||
"fieldsUi": {
|
||||
"values": [
|
||||
{
|
||||
"column": "name",
|
||||
"fieldValue": "Sam"
|
||||
},
|
||||
{
|
||||
"column": "age",
|
||||
"fieldValue": "34"
|
||||
},
|
||||
{
|
||||
"column": "data",
|
||||
"fieldValue": "data 4"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "0e0ac1d2-242c-486a-9287-c70307645acc",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [860, 140],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "worksheet",
|
||||
"operation": "update",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "={426949D7-797F-43A9-A8A4-8FE283495A82}",
|
||||
"mode": "id"
|
||||
},
|
||||
"dataMode": "autoMap",
|
||||
"columnToMatchOn": "id",
|
||||
"options": {}
|
||||
},
|
||||
"id": "d3209da3-cfaf-40a6-a318-c66c2931a28a",
|
||||
"name": "Microsoft Excel 3651",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [860, 380],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"data": {
|
||||
"id": 3,
|
||||
"name": "Don",
|
||||
"age": 37,
|
||||
"data": "data 44"
|
||||
}
|
||||
},
|
||||
"id": "eb908630-7324-46a5-890d-b5cfccf17cb2",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-testing.testData",
|
||||
"typeVersion": 1,
|
||||
"position": [660, 380]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"id": 3,
|
||||
"name": "Sam",
|
||||
"age": 34,
|
||||
"data": "data 4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Microsoft Excel 3651": [
|
||||
{
|
||||
"json": {
|
||||
"id": 3,
|
||||
"name": "Don",
|
||||
"age": 37,
|
||||
"data": "data 44"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 3651",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import { credentials } from '../../../credentials';
|
||||
|
||||
describe('Test MicrosoftExcelV2, worksheet => upsert', () => {
|
||||
nock('https://graph.microsoft.com/v1.0/me')
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/usedRange',
|
||||
)
|
||||
.reply(200, {
|
||||
address: 'Sheet4!A1:D6',
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
],
|
||||
})
|
||||
.patch(
|
||||
"/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7BA0883CFE-D27E-4ECC-B94B-981830AAD55B%7D/range(address='A1:D7')",
|
||||
)
|
||||
.reply(200, {
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
[4, 'Sam', 34, 'data 4'],
|
||||
],
|
||||
})
|
||||
.get(
|
||||
'/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B426949D7-797F-43A9-A8A4-8FE283495A82%7D/usedRange',
|
||||
)
|
||||
.reply(200, {
|
||||
address: 'Sheet4!A1:D6',
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
],
|
||||
})
|
||||
.patch(
|
||||
"/drive/items/01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I/workbook/worksheets/%7B426949D7-797F-43A9-A8A4-8FE283495A82%7D/range(address='A1:D7')",
|
||||
)
|
||||
.reply(200, {
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
[4, 'Don', 37, 'data 44'],
|
||||
],
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({
|
||||
credentials,
|
||||
workflowFiles: ['upsert.workflow.json'],
|
||||
});
|
||||
});
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
{
|
||||
"name": "My workflow 5",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"id": "f0857ec9-0709-4657-a2f4-059837c94060",
|
||||
"name": "When clicking \"Execute Workflow\"",
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [540, 220]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "worksheet",
|
||||
"operation": "upsert",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія",
|
||||
"cachedResultUrl": "https://5w1hb7-my.sharepoint.com/personal/michaeldevsandbox_5w1hb7_onmicrosoft_com/_layouts/15/Doc.aspx?sourcedoc=%7BECC4041C-3AB6-4CF7-B079-0926470A1388%7D&file=%D0%9F%D0%A0%D0%A0%D0%9E%20%D0%BA%D0%BE%D0%BF%D1%96%D1%8F.xlsx&action=default&mobileredirect=true&DefaultItemOpen=1"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "={A0883CFE-D27E-4ECC-B94B-981830AAD55B}",
|
||||
"mode": "id"
|
||||
},
|
||||
"columnToMatchOn": "id",
|
||||
"valueToMatchOn": "4",
|
||||
"fieldsUi": {
|
||||
"values": [
|
||||
{
|
||||
"column": "name",
|
||||
"fieldValue": "Sam"
|
||||
},
|
||||
{
|
||||
"column": "age",
|
||||
"fieldValue": "34"
|
||||
},
|
||||
{
|
||||
"column": "data",
|
||||
"fieldValue": "data 4"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
},
|
||||
"id": "426ed055-0c9b-4ae2-a9fe-a6cce875d5ee",
|
||||
"name": "Microsoft Excel 365",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [1020, 220],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"resource": "worksheet",
|
||||
"operation": "upsert",
|
||||
"workbook": {
|
||||
"__rl": true,
|
||||
"value": "01FUWX3BQ4ATCOZNR265GLA6IJEZDQUE4I",
|
||||
"mode": "list",
|
||||
"cachedResultName": "ПРРО копія",
|
||||
"cachedResultUrl": "https://5w1hb7-my.sharepoint.com/personal/michaeldevsandbox_5w1hb7_onmicrosoft_com/_layouts/15/Doc.aspx?sourcedoc=%7BECC4041C-3AB6-4CF7-B079-0926470A1388%7D&file=%D0%9F%D0%A0%D0%A0%D0%9E%20%D0%BA%D0%BE%D0%BF%D1%96%D1%8F.xlsx&action=default&mobileredirect=true&DefaultItemOpen=1"
|
||||
},
|
||||
"worksheet": {
|
||||
"__rl": true,
|
||||
"value": "={426949D7-797F-43A9-A8A4-8FE283495A82}",
|
||||
"mode": "id"
|
||||
},
|
||||
"dataMode": "autoMap",
|
||||
"columnToMatchOn": "id",
|
||||
"options": {}
|
||||
},
|
||||
"id": "0b10bfae-4e15-48c5-a2e6-7bec1c2687ec",
|
||||
"name": "Microsoft Excel 3651",
|
||||
"type": "n8n-nodes-base.microsoftExcel",
|
||||
"typeVersion": 2,
|
||||
"position": [1020, 460],
|
||||
"credentials": {
|
||||
"microsoftExcelOAuth2Api": {
|
||||
"id": "70",
|
||||
"name": "Microsoft Excel account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"data": {
|
||||
"id": 4,
|
||||
"name": "Don",
|
||||
"age": 37,
|
||||
"data": "data 44"
|
||||
}
|
||||
},
|
||||
"id": "93453ccb-5ac3-425b-8ac4-d20f0dfe9bab",
|
||||
"name": "Code",
|
||||
"type": "n8n-nodes-testing.testData",
|
||||
"typeVersion": 1,
|
||||
"position": [820, 460]
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"Microsoft Excel 365": [
|
||||
{
|
||||
"json": {
|
||||
"id": 4,
|
||||
"name": "Sam",
|
||||
"age": 34,
|
||||
"data": "data 4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"Microsoft Excel 3651": [
|
||||
{
|
||||
"json": {
|
||||
"id": 4,
|
||||
"name": "Don",
|
||||
"age": 37,
|
||||
"data": "data 44"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking \"Execute Workflow\"": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 365",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Code",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Code": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Microsoft Excel 3651",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {},
|
||||
"versionId": "f24273bf-ef07-49da-960b-a68b63961d4a",
|
||||
"id": "135",
|
||||
"meta": {
|
||||
"instanceId": "36203ea1ce3cef713fa25999bd9874ae26b9e4c2c3a90a365f2882a154d031d0"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,741 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import get from 'lodash/get';
|
||||
import { constructExecutionMetaData } from 'n8n-core';
|
||||
import type { IDataObject, IExecuteFunctions, IGetNodeParameterOptions, INode } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
checkRange,
|
||||
findAppendRange,
|
||||
nextExcelColumn,
|
||||
parseAddress,
|
||||
prepareOutput,
|
||||
updateByAutoMaping,
|
||||
updateByDefinedValues,
|
||||
} from '../../../v2/helpers/utils';
|
||||
|
||||
const node: INode = {
|
||||
id: '1',
|
||||
name: 'Microsoft Excel 365',
|
||||
typeVersion: 2,
|
||||
type: 'n8n-nodes-base.microsoftExcel',
|
||||
position: [60, 760],
|
||||
parameters: {},
|
||||
};
|
||||
|
||||
const fakeExecute = (nodeParameters: IDataObject[]) => {
|
||||
const fakeExecuteFunction = {
|
||||
getInputData() {
|
||||
return [{ json: {} }];
|
||||
},
|
||||
getNodeParameter(
|
||||
parameterName: string,
|
||||
itemIndex: number,
|
||||
fallbackValue?: IDataObject,
|
||||
options?: IGetNodeParameterOptions,
|
||||
) {
|
||||
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
|
||||
return get(nodeParameters[itemIndex], parameter, fallbackValue);
|
||||
},
|
||||
} as unknown as IExecuteFunctions;
|
||||
return fakeExecuteFunction;
|
||||
};
|
||||
|
||||
const responseData = {
|
||||
address: 'Sheet4!A1:D4',
|
||||
addressLocal: 'Sheet4!A1:D4',
|
||||
columnCount: 4,
|
||||
cellCount: 16,
|
||||
columnHidden: false,
|
||||
rowHidden: false,
|
||||
numberFormat: [
|
||||
['General', 'General', 'General', 'General'],
|
||||
['General', 'General', 'General', 'General'],
|
||||
['General', 'General', 'General', 'General'],
|
||||
['General', 'General', 'General', 'General'],
|
||||
],
|
||||
columnIndex: 0,
|
||||
text: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
['1', 'Sam', '33', 'data 1'],
|
||||
['2', 'Jon', '44', 'data 2'],
|
||||
['3', 'Ron', '55', 'data 3'],
|
||||
],
|
||||
formulas: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
],
|
||||
formulasLocal: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
],
|
||||
formulasR1C1: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
],
|
||||
hidden: false,
|
||||
rowCount: 4,
|
||||
rowIndex: 0,
|
||||
valueTypes: [
|
||||
['String', 'String', 'String', 'String'],
|
||||
['Double', 'String', 'Double', 'String'],
|
||||
['Double', 'String', 'Double', 'String'],
|
||||
['Double', 'String', 'Double', 'String'],
|
||||
],
|
||||
values: [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
],
|
||||
};
|
||||
|
||||
describe('Test MicrosoftExcelV2, prepareOutput', () => {
|
||||
const thisArg = mock<IExecuteFunctions>({
|
||||
helpers: mock({ constructExecutionMetaData }),
|
||||
getInputData() {
|
||||
return [{ json: {} }];
|
||||
},
|
||||
});
|
||||
|
||||
it('should return empty array', () => {
|
||||
const output = prepareOutput.call(thisArg, node, { values: [] }, { rawData: false });
|
||||
expect(output).toBeDefined();
|
||||
expect(output).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return raw response', () => {
|
||||
const output = prepareOutput.call(thisArg, node, responseData, { rawData: true });
|
||||
expect(output).toBeDefined();
|
||||
expect(output[0].json.data).toEqual(responseData);
|
||||
});
|
||||
|
||||
it('should return raw response in custom property', () => {
|
||||
const customKey = 'customKey';
|
||||
const output = prepareOutput.call(thisArg, node, responseData, {
|
||||
rawData: true,
|
||||
dataProperty: customKey,
|
||||
});
|
||||
expect(output).toBeDefined();
|
||||
expect(output[0].json.customKey).toEqual(responseData);
|
||||
});
|
||||
|
||||
it('should return formated response', () => {
|
||||
const output = prepareOutput.call(thisArg, node, responseData, { rawData: false });
|
||||
expect(output).toBeDefined();
|
||||
expect(output.length).toEqual(3);
|
||||
expect(output[0].json).toEqual({
|
||||
id: 1,
|
||||
name: 'Sam',
|
||||
age: 33,
|
||||
data: 'data 1',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return response with selected first data row', () => {
|
||||
const output = prepareOutput.call(thisArg, node, responseData, {
|
||||
rawData: false,
|
||||
firstDataRow: 3,
|
||||
});
|
||||
expect(output).toBeDefined();
|
||||
expect(output.length).toEqual(1);
|
||||
expect(output[0].json).toEqual({
|
||||
id: 3,
|
||||
name: 'Ron',
|
||||
age: 55,
|
||||
data: 'data 3',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return response with selected first data row', () => {
|
||||
const [firstRow, ...rest] = responseData.values;
|
||||
const response = { values: [...rest, firstRow] };
|
||||
const output = prepareOutput.call(thisArg, node, response, {
|
||||
rawData: false,
|
||||
keyRow: 3,
|
||||
firstDataRow: 0,
|
||||
});
|
||||
expect(output).toBeDefined();
|
||||
expect(output.length).toEqual(3);
|
||||
expect(output[0].json).toEqual({
|
||||
id: 1,
|
||||
name: 'Sam',
|
||||
age: 33,
|
||||
data: 'data 1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test MicrosoftExcelV2, updateByDefinedValues', () => {
|
||||
it('should update single row', () => {
|
||||
const nodeParameters = [
|
||||
{
|
||||
columnToMatchOn: 'id',
|
||||
valueToMatchOn: 2,
|
||||
fieldsUi: {
|
||||
values: [
|
||||
{
|
||||
column: 'name',
|
||||
fieldValue: 'Donald',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const sheetData = responseData.values;
|
||||
|
||||
const updateSummary = updateByDefinedValues.call(
|
||||
fakeExecute(nodeParameters),
|
||||
nodeParameters.length,
|
||||
sheetData,
|
||||
false,
|
||||
);
|
||||
|
||||
expect(updateSummary).toBeDefined();
|
||||
expect(updateSummary.updatedRows).toContain(0); //header row
|
||||
expect(updateSummary.updatedRows).toContain(2); //updated row
|
||||
expect(updateSummary.updatedRows).toHaveLength(2);
|
||||
expect(updateSummary.updatedData[2][1]).toEqual('Donald'); // updated value
|
||||
});
|
||||
|
||||
it('should update multiple rows', () => {
|
||||
const nodeParameters = [
|
||||
{
|
||||
columnToMatchOn: 'id',
|
||||
valueToMatchOn: 2,
|
||||
fieldsUi: {
|
||||
values: [
|
||||
{
|
||||
column: 'name',
|
||||
fieldValue: 'Donald',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
columnToMatchOn: 'id',
|
||||
valueToMatchOn: 3,
|
||||
fieldsUi: {
|
||||
values: [
|
||||
{
|
||||
column: 'name',
|
||||
fieldValue: 'Eduard',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
columnToMatchOn: 'id',
|
||||
valueToMatchOn: 4,
|
||||
fieldsUi: {
|
||||
values: [
|
||||
{
|
||||
column: 'name',
|
||||
fieldValue: 'Ismael',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const sheetData = [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
[4, 'Ron', 55, 'data 3'],
|
||||
];
|
||||
|
||||
const updateSummary = updateByDefinedValues.call(
|
||||
fakeExecute(nodeParameters),
|
||||
nodeParameters.length,
|
||||
sheetData,
|
||||
false,
|
||||
);
|
||||
|
||||
expect(updateSummary).toBeDefined();
|
||||
expect(updateSummary.updatedRows).toContain(0); //header row
|
||||
expect(updateSummary.updatedRows).toContain(2); //updated row
|
||||
expect(updateSummary.updatedRows).toContain(3); //updated row
|
||||
expect(updateSummary.updatedRows).toContain(4); //updated row
|
||||
expect(updateSummary.updatedRows).toHaveLength(4);
|
||||
expect(updateSummary.updatedData[2][1]).toEqual('Donald'); // updated value
|
||||
expect(updateSummary.updatedData[3][1]).toEqual('Eduard'); // updated value
|
||||
expect(updateSummary.updatedData[4][1]).toEqual('Ismael'); // updated value
|
||||
});
|
||||
|
||||
it('should update all occurances', () => {
|
||||
const nodeParameters = [
|
||||
{
|
||||
columnToMatchOn: 'data',
|
||||
valueToMatchOn: 'data 3',
|
||||
fieldsUi: {
|
||||
values: [
|
||||
{
|
||||
column: 'name',
|
||||
fieldValue: 'Donald',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const sheetData = [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 55, 'data 3'],
|
||||
[2, 'Jon', 77, 'data 3'],
|
||||
[3, 'Ron', 44, 'data 3'],
|
||||
[4, 'Ron', 33, 'data 3'],
|
||||
];
|
||||
|
||||
const updateSummary = updateByDefinedValues.call(
|
||||
fakeExecute(nodeParameters),
|
||||
nodeParameters.length,
|
||||
sheetData,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(updateSummary).toBeDefined();
|
||||
expect(updateSummary.updatedRows).toContain(0); //header row
|
||||
expect(updateSummary.updatedRows).toHaveLength(5);
|
||||
|
||||
for (let i = 1; i < updateSummary.updatedRows.length; i++) {
|
||||
expect(updateSummary.updatedData[i][1]).toEqual('Donald'); // updated value
|
||||
}
|
||||
});
|
||||
|
||||
it('should append rows', () => {
|
||||
const nodeParameters = [
|
||||
{
|
||||
columnToMatchOn: 'id',
|
||||
valueToMatchOn: 4,
|
||||
fieldsUi: {
|
||||
values: [
|
||||
{
|
||||
column: 'name',
|
||||
fieldValue: 'Donald',
|
||||
},
|
||||
{
|
||||
column: 'age',
|
||||
fieldValue: 45,
|
||||
},
|
||||
{
|
||||
column: 'data',
|
||||
fieldValue: 'data 4',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
columnToMatchOn: 'id',
|
||||
valueToMatchOn: 5,
|
||||
fieldsUi: {
|
||||
values: [
|
||||
{
|
||||
column: 'name',
|
||||
fieldValue: 'Victor',
|
||||
},
|
||||
{
|
||||
column: 'age',
|
||||
fieldValue: 67,
|
||||
},
|
||||
{
|
||||
column: 'data',
|
||||
fieldValue: 'data 5',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const sheetData = [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 55, 'data 3'],
|
||||
[2, 'Jon', 77, 'data 3'],
|
||||
[3, 'Ron', 44, 'data 3'],
|
||||
];
|
||||
|
||||
const updateSummary = updateByDefinedValues.call(
|
||||
fakeExecute(nodeParameters),
|
||||
nodeParameters.length,
|
||||
sheetData,
|
||||
true,
|
||||
);
|
||||
|
||||
expect(updateSummary).toBeDefined();
|
||||
expect(updateSummary.updatedRows).toContain(0);
|
||||
expect(updateSummary.updatedRows.length).toEqual(1);
|
||||
expect(updateSummary.appendData[0]).toEqual({ id: 4, name: 'Donald', age: 45, data: 'data 4' });
|
||||
expect(updateSummary.appendData[1]).toEqual({ id: 5, name: 'Victor', age: 67, data: 'data 5' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test MicrosoftExcelV2, updateByAutoMaping', () => {
|
||||
it('should update single row', () => {
|
||||
const items = [
|
||||
{
|
||||
json: {
|
||||
id: 2,
|
||||
name: 'Donald',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const sheetData = [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
];
|
||||
|
||||
const updateSummary = updateByAutoMaping(items, sheetData, 'id');
|
||||
|
||||
expect(updateSummary).toBeDefined();
|
||||
expect(updateSummary.updatedRows).toHaveLength(2);
|
||||
expect(updateSummary.updatedRows).toContain(0); //header row
|
||||
expect(updateSummary.updatedRows).toContain(2); //updated row
|
||||
expect(updateSummary.updatedData[2][1]).toEqual('Donald'); // updated value
|
||||
});
|
||||
|
||||
it('should append single row', () => {
|
||||
const items = [
|
||||
{
|
||||
json: {
|
||||
id: 5,
|
||||
name: 'Donald',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const sheetData = [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
];
|
||||
|
||||
const updateSummary = updateByAutoMaping(items, sheetData, 'id');
|
||||
|
||||
expect(updateSummary).toBeDefined();
|
||||
expect(updateSummary.updatedRows).toHaveLength(1);
|
||||
expect(updateSummary.updatedRows).toContain(0); //header row
|
||||
expect(updateSummary.appendData[0]).toEqual({ id: 5, name: 'Donald' });
|
||||
});
|
||||
|
||||
it('should append skip row with match column undefined', () => {
|
||||
const items = [
|
||||
{
|
||||
json: {
|
||||
id: 5,
|
||||
name: 'Donald',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const sheetData = [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
];
|
||||
|
||||
const updateSummary = updateByAutoMaping(items, sheetData, 'idd');
|
||||
|
||||
expect(updateSummary).toBeDefined();
|
||||
expect(updateSummary.updatedRows).toHaveLength(1);
|
||||
expect(updateSummary.updatedRows).toContain(0); //header row
|
||||
expect(updateSummary.appendData.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should update multiple rows', () => {
|
||||
const items = [
|
||||
{
|
||||
json: {
|
||||
id: 2,
|
||||
name: 'Donald',
|
||||
},
|
||||
},
|
||||
{
|
||||
json: {
|
||||
id: 3,
|
||||
name: 'Eduard',
|
||||
},
|
||||
},
|
||||
{
|
||||
json: {
|
||||
id: 4,
|
||||
name: 'Ismael',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const sheetData = [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 33, 'data 1'],
|
||||
[2, 'Jon', 44, 'data 2'],
|
||||
[3, 'Ron', 55, 'data 3'],
|
||||
[4, 'Ron', 55, 'data 3'],
|
||||
];
|
||||
|
||||
const updateSummary = updateByAutoMaping(items, sheetData, 'id');
|
||||
|
||||
expect(updateSummary).toBeDefined();
|
||||
expect(updateSummary.updatedRows).toContain(0); //header row
|
||||
expect(updateSummary.updatedRows).toContain(2); //updated row
|
||||
expect(updateSummary.updatedRows).toContain(3); //updated row
|
||||
expect(updateSummary.updatedRows).toContain(4); //updated row
|
||||
expect(updateSummary.updatedRows).toHaveLength(4);
|
||||
expect(updateSummary.updatedData[2][1]).toEqual('Donald'); // updated value
|
||||
expect(updateSummary.updatedData[3][1]).toEqual('Eduard'); // updated value
|
||||
expect(updateSummary.updatedData[4][1]).toEqual('Ismael'); // updated value
|
||||
});
|
||||
|
||||
it('should update all occurrences', () => {
|
||||
const items = [
|
||||
{
|
||||
json: {
|
||||
data: 'data 3',
|
||||
name: 'Donald',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const sheetData = [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 55, 'data 3'],
|
||||
[2, 'Jon', 77, 'data 3'],
|
||||
[3, 'Ron', 44, 'data 3'],
|
||||
[4, 'Ron', 33, 'data 3'],
|
||||
];
|
||||
|
||||
const updateSummary = updateByAutoMaping(items, sheetData, 'data', true);
|
||||
|
||||
expect(updateSummary).toBeDefined();
|
||||
expect(updateSummary.updatedRows).toContain(0); //header row
|
||||
expect(updateSummary.updatedRows).toHaveLength(5);
|
||||
|
||||
for (let i = 1; i < updateSummary.updatedRows.length; i++) {
|
||||
expect(updateSummary.updatedData[i][1]).toEqual('Donald'); // updated value
|
||||
}
|
||||
});
|
||||
|
||||
it('should append rows', () => {
|
||||
const items = [
|
||||
{
|
||||
json: {
|
||||
id: 4,
|
||||
data: 'data 4',
|
||||
name: 'Donald',
|
||||
age: 45,
|
||||
},
|
||||
},
|
||||
{
|
||||
json: {
|
||||
id: 5,
|
||||
data: 'data 5',
|
||||
name: 'Victor',
|
||||
age: 67,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const sheetData = [
|
||||
['id', 'name', 'age', 'data'],
|
||||
[1, 'Sam', 55, 'data 3'],
|
||||
[2, 'Jon', 77, 'data 3'],
|
||||
[3, 'Ron', 44, 'data 3'],
|
||||
];
|
||||
|
||||
const updateSummary = updateByAutoMaping(items, sheetData, 'data', true);
|
||||
|
||||
expect(updateSummary).toBeDefined();
|
||||
expect(updateSummary.updatedRows).toContain(0);
|
||||
expect(updateSummary.updatedRows.length).toEqual(1);
|
||||
expect(updateSummary.appendData[0]).toEqual({ id: 4, name: 'Donald', age: 45, data: 'data 4' });
|
||||
expect(updateSummary.appendData[1]).toEqual({ id: 5, name: 'Victor', age: 67, data: 'data 5' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test MicrosoftExcelV2, checkRange', () => {
|
||||
it('should not throw error', () => {
|
||||
const range = 'A1:D4';
|
||||
expect(() => {
|
||||
checkRange(node, range);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should throw error', () => {
|
||||
const range = 'A:D';
|
||||
expect(() => {
|
||||
checkRange(node, range);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test MicrosoftExcelV2, findAppendRange', () => {
|
||||
it('should find append range for empty table', () => {
|
||||
const address = 'A1';
|
||||
const cols = 2;
|
||||
const rows = 3;
|
||||
const result = findAppendRange(address, { cols, rows });
|
||||
expect(result).toBe('A1:B3');
|
||||
});
|
||||
|
||||
it('should find append range for filled table', () => {
|
||||
const address = 'A1:B2';
|
||||
const cols = 2;
|
||||
const rows = 2;
|
||||
const result = findAppendRange(address, { cols, rows });
|
||||
expect(result).toBe('A3:B4');
|
||||
});
|
||||
|
||||
it('should find append range with additional columns for filled table', () => {
|
||||
const address = 'A1:B2';
|
||||
const cols = 3;
|
||||
const rows = 2;
|
||||
const result = findAppendRange(address, { cols, rows });
|
||||
expect(result).toBe('A3:C4');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test MicrosoftExcelV2, nextExcelColumn', () => {
|
||||
it('should return same column with offset 0', () => {
|
||||
const result = nextExcelColumn('A', 0);
|
||||
expect(result).toBe('A');
|
||||
});
|
||||
|
||||
it('should return next column with default offset', () => {
|
||||
const result = nextExcelColumn('A');
|
||||
expect(result).toBe('B');
|
||||
});
|
||||
|
||||
it('should return next column with offset 2', () => {
|
||||
const result = nextExcelColumn('A', 2);
|
||||
expect(result).toBe('C');
|
||||
});
|
||||
|
||||
it('should handle Z to AA transition', () => {
|
||||
const result = nextExcelColumn('Z');
|
||||
expect(result).toBe('AA');
|
||||
});
|
||||
|
||||
it('should handle AZ with offset 2', () => {
|
||||
const result = nextExcelColumn('AZ', 2);
|
||||
expect(result).toBe('BB');
|
||||
});
|
||||
|
||||
it('should handle ZZ with offset 5', () => {
|
||||
const result = nextExcelColumn('ZZ', 5);
|
||||
expect(result).toBe('AAE');
|
||||
});
|
||||
|
||||
it('should handle single letter columns', () => {
|
||||
expect(nextExcelColumn('B')).toBe('C');
|
||||
expect(nextExcelColumn('Y')).toBe('Z');
|
||||
expect(nextExcelColumn('M', 3)).toBe('P');
|
||||
});
|
||||
|
||||
it('should handle double letter columns', () => {
|
||||
expect(nextExcelColumn('AA')).toBe('AB');
|
||||
expect(nextExcelColumn('AB')).toBe('AC');
|
||||
expect(nextExcelColumn('BA')).toBe('BB');
|
||||
expect(nextExcelColumn('AY')).toBe('AZ');
|
||||
});
|
||||
|
||||
it('should handle triple letter columns', () => {
|
||||
expect(nextExcelColumn('AAA')).toBe('AAB');
|
||||
expect(nextExcelColumn('AAZ')).toBe('ABA');
|
||||
expect(nextExcelColumn('AZZ')).toBe('BAA');
|
||||
});
|
||||
|
||||
it('should handle large offsets', () => {
|
||||
expect(nextExcelColumn('A', 25)).toBe('Z');
|
||||
expect(nextExcelColumn('A', 26)).toBe('AA');
|
||||
expect(nextExcelColumn('A', 27)).toBe('AB');
|
||||
expect(nextExcelColumn('A', 51)).toBe('AZ');
|
||||
expect(nextExcelColumn('A', 52)).toBe('BA');
|
||||
});
|
||||
|
||||
it('should handle transitions at column boundaries', () => {
|
||||
expect(nextExcelColumn('Z', 1)).toBe('AA');
|
||||
expect(nextExcelColumn('Z', 2)).toBe('AB');
|
||||
expect(nextExcelColumn('AZ', 1)).toBe('BA');
|
||||
expect(nextExcelColumn('ZZ', 1)).toBe('AAA');
|
||||
});
|
||||
|
||||
it('should handle very large columns', () => {
|
||||
expect(nextExcelColumn('XFD', 1)).toBe('XFE'); // XFD is Excel's last column
|
||||
expect(nextExcelColumn('ZZY', 1)).toBe('ZZZ');
|
||||
});
|
||||
|
||||
it('should handle offset of 1 explicitly', () => {
|
||||
expect(nextExcelColumn('A', 1)).toBe('B');
|
||||
expect(nextExcelColumn('Z', 1)).toBe('AA');
|
||||
expect(nextExcelColumn('AA', 1)).toBe('AB');
|
||||
});
|
||||
|
||||
it('should throw error for invalid offset', () => {
|
||||
expect(() => nextExcelColumn('A', -1)).toThrow('Invalid offset: -1');
|
||||
});
|
||||
|
||||
it('should maintain column sequence continuity', () => {
|
||||
let current = 'A';
|
||||
const sequence = [current];
|
||||
|
||||
for (let i = 0; i < 30; i++) {
|
||||
current = nextExcelColumn(current);
|
||||
sequence.push(current);
|
||||
}
|
||||
|
||||
// Verify some key transitions in the sequence
|
||||
expect(sequence).toContain('Z');
|
||||
expect(sequence).toContain('AA');
|
||||
expect(sequence).toContain('AB');
|
||||
|
||||
// Verify Z is followed by AA
|
||||
const zIndex = sequence.indexOf('Z');
|
||||
expect(sequence[zIndex + 1]).toBe('AA');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test MicrosoftExcelV2, parseAddress', () => {
|
||||
it('should parse normal address', () => {
|
||||
const address = 'A1:B2';
|
||||
const result = parseAddress(address);
|
||||
expect(result.cellFrom.value).toBe('A1');
|
||||
expect(result.cellFrom.column).toBe('A');
|
||||
expect(result.cellFrom.row).toBe('1');
|
||||
expect(result.cellTo.value).toBe('B2');
|
||||
expect(result.cellTo.column).toBe('B');
|
||||
expect(result.cellTo.row).toBe('2');
|
||||
});
|
||||
|
||||
it('should parse address with sheet name', () => {
|
||||
const address = 'Sheet1!A1:B2';
|
||||
const result = parseAddress(address);
|
||||
expect(result.cellFrom.value).toBe('A1');
|
||||
expect(result.cellFrom.column).toBe('A');
|
||||
expect(result.cellFrom.row).toBe('1');
|
||||
expect(result.cellTo.value).toBe('B2');
|
||||
expect(result.cellTo.column).toBe('B');
|
||||
expect(result.cellTo.row).toBe('2');
|
||||
});
|
||||
|
||||
it('should parse address with double letter cell', () => {
|
||||
const address = 'A1:AA2';
|
||||
const result = parseAddress(address);
|
||||
expect(result.cellFrom.value).toBe('A1');
|
||||
expect(result.cellFrom.column).toBe('A');
|
||||
expect(result.cellFrom.row).toBe('1');
|
||||
|
||||
expect(result.cellTo.value).toBe('AA2');
|
||||
expect(result.cellTo.column).toBe('AA');
|
||||
expect(result.cellTo.row).toBe('2');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IHttpRequestMethods,
|
||||
ILoadOptionsFunctions,
|
||||
IRequestOptions,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
export async function microsoftApiRequest(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
resource: string,
|
||||
|
||||
body: any = {},
|
||||
qs: IDataObject = {},
|
||||
uri?: string,
|
||||
headers: IDataObject = {},
|
||||
): Promise<any> {
|
||||
const options: IRequestOptions = {
|
||||
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);
|
||||
}
|
||||
|
||||
return await this.helpers.requestOAuth2.call(this, 'microsoftExcelOAuth2Api', options);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
export async function microsoftApiRequestAllItems(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
propertyName: string,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
|
||||
body: any = {},
|
||||
query: IDataObject = {},
|
||||
): Promise<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'];
|
||||
if (uri?.includes('$top')) {
|
||||
delete query.$top;
|
||||
}
|
||||
returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
|
||||
} while (responseData['@odata.nextLink'] !== undefined);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function microsoftApiRequestAllItemsSkip(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
propertyName: string,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
|
||||
body: any = {},
|
||||
query: IDataObject = {},
|
||||
): Promise<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] as IDataObject[]);
|
||||
} while (responseData.value.length !== 0);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,689 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeBaseDescription,
|
||||
INodeTypeDescription,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError, NodeConnectionTypes } from 'n8n-workflow';
|
||||
|
||||
import { oldVersionNotice } from '@utils/descriptions';
|
||||
|
||||
import {
|
||||
microsoftApiRequest,
|
||||
microsoftApiRequestAllItems,
|
||||
microsoftApiRequestAllItemsSkip,
|
||||
} from './GenericFunctions';
|
||||
import { tableFields, tableOperations } from './TableDescription';
|
||||
import { workbookFields, workbookOperations } from './WorkbookDescription';
|
||||
import { worksheetFields, worksheetOperations } from './WorksheetDescription';
|
||||
import { generatePairedItemData } from '../../../../utils/utilities';
|
||||
|
||||
const versionDescription: INodeTypeDescription = {
|
||||
displayName: 'Microsoft Excel',
|
||||
name: 'microsoftExcel',
|
||||
icon: 'file:excel.svg',
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Microsoft Excel API',
|
||||
defaults: {
|
||||
name: 'Microsoft Excel',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'microsoftExcelOAuth2Api',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
oldVersionNotice,
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
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',
|
||||
},
|
||||
...workbookOperations,
|
||||
...workbookFields,
|
||||
...worksheetOperations,
|
||||
...worksheetFields,
|
||||
...tableOperations,
|
||||
...tableFields,
|
||||
],
|
||||
};
|
||||
export class MicrosoftExcelV1 implements INodeType {
|
||||
description: INodeTypeDescription;
|
||||
|
||||
constructor(baseDescription: INodeTypeBaseDescription) {
|
||||
this.description = {
|
||||
...baseDescription,
|
||||
...versionDescription,
|
||||
};
|
||||
}
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the workbooks to display them to user so that he can
|
||||
// select them easily
|
||||
async getWorkbooks(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
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<INodePropertyOptions[]> {
|
||||
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<INodePropertyOptions[]> {
|
||||
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<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const itemData = generatePairedItemData(items.length);
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const length = items.length;
|
||||
let qs: IDataObject = {};
|
||||
const result: IDataObject[] = [];
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0);
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
|
||||
if (resource === 'table') {
|
||||
//https://docs.microsoft.com/en-us/graph/api/table-post-rows?view=graph-rest-1.0&tabs=http
|
||||
if (operation === 'addRow') {
|
||||
try {
|
||||
// TODO: At some point it should be possible to use item dependent parameters.
|
||||
// Is however important to then not make one separate request each.
|
||||
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);
|
||||
const body: IDataObject = {};
|
||||
|
||||
if (additionalFields.index) {
|
||||
body.index = additionalFields.index as number;
|
||||
}
|
||||
|
||||
// Get table columns to eliminate any columns not needed on the input
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
const columns = responseData.value.map((column: IDataObject) => column.name);
|
||||
|
||||
const rows: any[][] = [];
|
||||
|
||||
// Bring the items into the correct format
|
||||
for (const item of items) {
|
||||
const row = [];
|
||||
for (const column of columns) {
|
||||
row.push(item.json[column]);
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
|
||||
body.values = rows;
|
||||
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 },
|
||||
);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
{ itemData },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
//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++) {
|
||||
try {
|
||||
qs = {};
|
||||
const workbookId = this.getNodeParameter('workbook', i) as string;
|
||||
const worksheetId = this.getNodeParameter('worksheet', i) as string;
|
||||
const tableId = this.getNodeParameter('table', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', i);
|
||||
const rawData = this.getNodeParameter('rawData', i);
|
||||
if (rawData) {
|
||||
const filters = this.getNodeParameter('filters', i);
|
||||
if (filters.fields) {
|
||||
qs.$select = filters.fields;
|
||||
}
|
||||
}
|
||||
if (returnAll) {
|
||||
responseData = await microsoftApiRequestAllItemsSkip.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', i);
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
if (!rawData) {
|
||||
responseData = responseData.map((column: IDataObject) => ({ name: column.name }));
|
||||
} else {
|
||||
const dataProperty = this.getNodeParameter('dataProperty', i) as string;
|
||||
responseData = { [dataProperty]: responseData };
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
//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++) {
|
||||
qs = {};
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i) as string;
|
||||
const worksheetId = this.getNodeParameter('worksheet', i) as string;
|
||||
const tableId = this.getNodeParameter('table', i) as string;
|
||||
const returnAll = this.getNodeParameter('returnAll', i);
|
||||
const rawData = this.getNodeParameter('rawData', i);
|
||||
if (rawData) {
|
||||
const filters = this.getNodeParameter('filters', i);
|
||||
if (filters.fields) {
|
||||
qs.$select = filters.fields;
|
||||
}
|
||||
}
|
||||
if (returnAll) {
|
||||
responseData = await microsoftApiRequestAllItemsSkip.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
const rowsQs = { ...qs };
|
||||
rowsQs.$top = this.getNodeParameter('limit', i);
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`,
|
||||
{},
|
||||
rowsQs,
|
||||
);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
if (!rawData) {
|
||||
const columnsQs = { ...qs };
|
||||
columnsQs.$select = 'name';
|
||||
// TODO: That should probably be cached in the future
|
||||
let columns = await microsoftApiRequestAllItemsSkip.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
|
||||
{},
|
||||
columnsQs,
|
||||
);
|
||||
|
||||
columns = (columns as IDataObject[]).map((column) => column.name);
|
||||
for (let index = 0; index < responseData.length; index++) {
|
||||
const object: IDataObject = {};
|
||||
for (let y = 0; y < columns.length; y++) {
|
||||
object[columns[y]] = responseData[index].values[0][y];
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ ...object }),
|
||||
{ itemData: { item: index } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} else {
|
||||
const dataProperty = this.getNodeParameter('dataProperty', i) as string;
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ [dataProperty]: responseData }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (operation === 'lookup') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
qs = {};
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i) as string;
|
||||
const worksheetId = this.getNodeParameter('worksheet', i) as string;
|
||||
const tableId = this.getNodeParameter('table', i) as string;
|
||||
const lookupColumn = this.getNodeParameter('lookupColumn', i) as string;
|
||||
const lookupValue = this.getNodeParameter('lookupValue', i) as string;
|
||||
const options = this.getNodeParameter('options', i);
|
||||
|
||||
responseData = await microsoftApiRequestAllItemsSkip.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`,
|
||||
{},
|
||||
{},
|
||||
);
|
||||
|
||||
qs.$select = 'name';
|
||||
// TODO: That should probably be cached in the future
|
||||
let columns = await microsoftApiRequestAllItemsSkip.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
columns = columns.map((column: IDataObject) => column.name);
|
||||
|
||||
if (!columns.includes(lookupColumn)) {
|
||||
throw new NodeApiError(this.getNode(), responseData as JsonObject, {
|
||||
message: `Column ${lookupColumn} does not exist on the table selected`,
|
||||
});
|
||||
}
|
||||
|
||||
result.length = 0;
|
||||
for (let index = 0; index < responseData.length; index++) {
|
||||
const object: IDataObject = {};
|
||||
for (let y = 0; y < columns.length; y++) {
|
||||
object[columns[y]] = responseData[index].values[0][y];
|
||||
}
|
||||
result.push({ ...object });
|
||||
}
|
||||
|
||||
if (options.returnAllMatches) {
|
||||
responseData = result.filter((data: IDataObject) => {
|
||||
return data[lookupColumn]?.toString() === lookupValue;
|
||||
});
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} else {
|
||||
responseData = result.find((data: IDataObject) => {
|
||||
return data[lookupColumn]?.toString() === lookupValue;
|
||||
});
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'workbook') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
qs = {};
|
||||
try {
|
||||
//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);
|
||||
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);
|
||||
const filters = this.getNodeParameter('filters', i);
|
||||
if (filters.fields) {
|
||||
qs.$select = filters.fields;
|
||||
}
|
||||
if (returnAll) {
|
||||
responseData = await microsoftApiRequestAllItems.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
"/drive/root/search(q='.xlsx')",
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', i);
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
"/drive/root/search(q='.xlsx')",
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(responseData)) {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} else if (responseData !== undefined) {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resource === 'worksheet') {
|
||||
for (let i = 0; i < length; i++) {
|
||||
qs = {};
|
||||
try {
|
||||
//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);
|
||||
const workbookId = this.getNodeParameter('workbook', i) as string;
|
||||
const filters = this.getNodeParameter('filters', i);
|
||||
if (filters.fields) {
|
||||
qs.$select = filters.fields;
|
||||
}
|
||||
if (returnAll) {
|
||||
responseData = await microsoftApiRequestAllItems.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', i);
|
||||
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);
|
||||
if (rawData) {
|
||||
const filters = this.getNodeParameter('filters', i);
|
||||
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 NodeApiError(this.getNode(), responseData as JsonObject, {
|
||||
message: 'Range did not return data',
|
||||
});
|
||||
}
|
||||
const keyValues = responseData.values[keyRow];
|
||||
for (let index = dataStartRow; index < responseData.values.length; index++) {
|
||||
const object: IDataObject = {};
|
||||
for (let y = 0; y < keyValues.length; y++) {
|
||||
object[keyValues[y]] = responseData.values[index][y];
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ ...object }),
|
||||
{ itemData: { item: index } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} else {
|
||||
const dataProperty = this.getNodeParameter('dataProperty', i) as string;
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ [dataProperty]: responseData }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,522 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const tableOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add Row',
|
||||
value: 'addRow',
|
||||
description: 'Adds rows to the end of the table',
|
||||
action: 'Add a row',
|
||||
},
|
||||
{
|
||||
name: 'Get Columns',
|
||||
value: 'getColumns',
|
||||
description: 'Retrieve a list of tablecolumns',
|
||||
action: 'Get columns',
|
||||
},
|
||||
{
|
||||
name: 'Get Rows',
|
||||
value: 'getRows',
|
||||
description: 'Retrieve a list of tablerows',
|
||||
action: 'Get rows',
|
||||
},
|
||||
{
|
||||
name: 'Lookup',
|
||||
value: 'lookup',
|
||||
description: 'Looks for a specific column value and then returns the matching row',
|
||||
action: 'Look up a column',
|
||||
},
|
||||
],
|
||||
default: 'addRow',
|
||||
},
|
||||
];
|
||||
|
||||
export const tableFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* table:addRow */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Workbook Name or ID',
|
||||
name: 'workbook',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getWorkbooks',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['addRow'],
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Worksheet Name or ID',
|
||||
name: 'worksheet',
|
||||
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: {
|
||||
loadOptionsMethod: 'getworksheets',
|
||||
loadOptionsDependsOn: ['workbook'],
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['addRow'],
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Table Name or ID',
|
||||
name: 'table',
|
||||
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: {
|
||||
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 or ID',
|
||||
name: 'workbook',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getWorkbooks',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getRows'],
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Worksheet Name or ID',
|
||||
name: 'worksheet',
|
||||
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: {
|
||||
loadOptionsMethod: 'getworksheets',
|
||||
loadOptionsDependsOn: ['workbook'],
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getRows'],
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Table Name or ID',
|
||||
name: 'table',
|
||||
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: {
|
||||
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: 'Whether to return all results 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: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getRows'],
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getRows'],
|
||||
resource: ['table'],
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
{
|
||||
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 or ID',
|
||||
name: 'workbook',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getWorkbooks',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getColumns'],
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Worksheet Name or ID',
|
||||
name: 'worksheet',
|
||||
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: {
|
||||
loadOptionsMethod: 'getworksheets',
|
||||
loadOptionsDependsOn: ['workbook'],
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getColumns'],
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Table Name or ID',
|
||||
name: 'table',
|
||||
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: {
|
||||
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: 'Whether to return all results 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: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getColumns'],
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getColumns'],
|
||||
resource: ['table'],
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
{
|
||||
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 ,.',
|
||||
},
|
||||
],
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* table:lookup */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Workbook Name or ID',
|
||||
name: 'workbook',
|
||||
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: {
|
||||
loadOptionsMethod: 'getWorkbooks',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['lookup'],
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Worksheet Name or ID',
|
||||
name: 'worksheet',
|
||||
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: {
|
||||
loadOptionsMethod: 'getworksheets',
|
||||
loadOptionsDependsOn: ['workbook'],
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['lookup'],
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Table Name or ID',
|
||||
name: 'table',
|
||||
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: {
|
||||
loadOptionsMethod: 'getTables',
|
||||
loadOptionsDependsOn: ['worksheet'],
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['lookup'],
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Lookup Column',
|
||||
name: 'lookupColumn',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'Email',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['table'],
|
||||
operation: ['lookup'],
|
||||
},
|
||||
},
|
||||
description: 'The name of the column in which to look for value',
|
||||
},
|
||||
{
|
||||
displayName: 'Lookup Value',
|
||||
name: 'lookupValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'frank@example.com',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['table'],
|
||||
operation: ['lookup'],
|
||||
},
|
||||
},
|
||||
description: 'The value to look for in column',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['table'],
|
||||
operation: ['lookup'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Return All Matches',
|
||||
name: 'returnAllMatches',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description:
|
||||
'By default only the first result gets returned. If options gets set all found matches get returned.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,133 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const workbookOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['workbook'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add Worksheet',
|
||||
value: 'addWorksheet',
|
||||
description: 'Adds a new worksheet to the workbook',
|
||||
action: 'Add a worksheet to a workbook',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get data of many workbooks',
|
||||
action: 'Get many workbooks',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
];
|
||||
|
||||
export const workbookFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* workbook:addWorksheet */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Workbook Name or ID',
|
||||
name: 'workbook',
|
||||
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: {
|
||||
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: 'Whether to return all results 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: 'Max number of 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 ,.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,252 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const worksheetOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get many worksheets',
|
||||
action: 'Get many worksheets',
|
||||
},
|
||||
{
|
||||
name: 'Get Content',
|
||||
value: 'getContent',
|
||||
description: 'Get worksheet content',
|
||||
action: 'Get a worksheet',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
},
|
||||
];
|
||||
|
||||
export const worksheetFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* worksheet:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Workbook Name or ID',
|
||||
name: 'workbook',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
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: 'Whether to return all results 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: 'Max number of 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 or ID',
|
||||
name: 'workbook',
|
||||
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: {
|
||||
loadOptionsMethod: 'getWorkbooks',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getContent'],
|
||||
resource: ['worksheet'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Worksheet Name or ID',
|
||||
name: 'worksheet',
|
||||
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: {
|
||||
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:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getContent'],
|
||||
resource: ['worksheet'],
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
{
|
||||
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 ,.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,28 @@
|
||||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeType,
|
||||
INodeTypeBaseDescription,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { router } from './actions/router';
|
||||
import { versionDescription } from './actions/versionDescription';
|
||||
import { listSearch, loadOptions } from './methods';
|
||||
|
||||
export class MicrosoftExcelV2 implements INodeType {
|
||||
description: INodeTypeDescription;
|
||||
|
||||
constructor(baseDescription: INodeTypeBaseDescription) {
|
||||
this.description = {
|
||||
...baseDescription,
|
||||
...versionDescription,
|
||||
usableAsTool: true,
|
||||
};
|
||||
}
|
||||
|
||||
methods = { listSearch, loadOptions };
|
||||
|
||||
async execute(this: IExecuteFunctions) {
|
||||
return await router.call(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const workbookRLC: INodeProperties = {
|
||||
displayName: 'Workbook',
|
||||
name: 'workbook',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'searchWorkbooks',
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[a-zA-Z0-9]{2,}',
|
||||
errorMessage: 'Not a valid Workbook ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const worksheetRLC: INodeProperties = {
|
||||
displayName: 'Sheet',
|
||||
name: 'worksheet',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'getWorksheetsList',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '{[a-zA-Z0-9\\-_]{2,}}',
|
||||
errorMessage: 'Not a valid Sheet ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const tableRLC: INodeProperties = {
|
||||
displayName: 'Table',
|
||||
name: 'table',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
modes: [
|
||||
{
|
||||
displayName: 'From List',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
typeOptions: {
|
||||
searchListMethod: 'getWorksheetTables',
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By ID',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '{[a-zA-Z0-9\\-_]{2,}}',
|
||||
errorMessage: 'Not a valid Table ID',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const rawDataOutput: INodeProperties = {
|
||||
displayName: 'Raw Data Output',
|
||||
name: 'rawDataOutput',
|
||||
type: 'fixedCollection',
|
||||
default: { values: { rawData: false } },
|
||||
options: [
|
||||
{
|
||||
displayName: 'Values',
|
||||
name: 'values',
|
||||
values: [
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-default-wrong-for-boolean
|
||||
default: 0,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
hide: {
|
||||
'/dataMode': ['nothing'],
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { AllEntities, Entity } from 'n8n-workflow';
|
||||
|
||||
type MicrosoftExcelMap = {
|
||||
table:
|
||||
| 'append'
|
||||
| 'addTable'
|
||||
| 'convertToRange'
|
||||
| 'deleteTable'
|
||||
| 'getColumns'
|
||||
| 'getRows'
|
||||
| 'lookup';
|
||||
workbook: 'addWorksheet' | 'deleteWorkbook' | 'getAll';
|
||||
worksheet: 'append' | 'clear' | 'deleteWorksheet' | 'getAll' | 'readRows' | 'update' | 'upsert';
|
||||
};
|
||||
|
||||
export type MicrosoftExcel = AllEntities<MicrosoftExcelMap>;
|
||||
|
||||
export type MicrosoftExcelChannel = Entity<MicrosoftExcelMap, 'table'>;
|
||||
export type MicrosoftExcelMessage = Entity<MicrosoftExcelMap, 'workbook'>;
|
||||
export type MicrosoftExcelMember = Entity<MicrosoftExcelMap, 'worksheet'>;
|
||||
@@ -0,0 +1,36 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import type { MicrosoftExcel } from './node.type';
|
||||
import * as table from './table/Table.resource';
|
||||
import * as workbook from './workbook/Workbook.resource';
|
||||
import * as worksheet from './worksheet/Worksheet.resource';
|
||||
|
||||
export async function router(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
let returnData: INodeExecutionData[] = [];
|
||||
|
||||
const resource = this.getNodeParameter<MicrosoftExcel>('resource', 0);
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
|
||||
const microsoftExcel = {
|
||||
resource,
|
||||
operation,
|
||||
} as MicrosoftExcel;
|
||||
|
||||
switch (microsoftExcel.resource) {
|
||||
case 'table':
|
||||
returnData = await table[microsoftExcel.operation].execute.call(this, items);
|
||||
break;
|
||||
case 'workbook':
|
||||
returnData = await workbook[microsoftExcel.operation].execute.call(this, items);
|
||||
break;
|
||||
case 'worksheet':
|
||||
returnData = await worksheet[microsoftExcel.operation].execute.call(this, items);
|
||||
break;
|
||||
default:
|
||||
throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known`);
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as addTable from './addTable.operation';
|
||||
import * as append from './append.operation';
|
||||
import * as convertToRange from './convertToRange.operation';
|
||||
import * as deleteTable from './deleteTable.operation';
|
||||
import * as getColumns from './getColumns.operation';
|
||||
import * as getRows from './getRows.operation';
|
||||
import * as lookup from './lookup.operation';
|
||||
|
||||
export { append, addTable, convertToRange, deleteTable, getColumns, getRows, lookup };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['table'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Append',
|
||||
value: 'append',
|
||||
description: 'Add rows to the end of the table',
|
||||
action: 'Append rows to table',
|
||||
},
|
||||
{
|
||||
name: 'Convert to Range',
|
||||
value: 'convertToRange',
|
||||
description: 'Convert a table to a range',
|
||||
action: 'Convert to range',
|
||||
},
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'addTable',
|
||||
description: 'Add a table based on range',
|
||||
action: 'Create a table',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'deleteTable',
|
||||
description: 'Delete a table',
|
||||
action: 'Delete a table',
|
||||
},
|
||||
{
|
||||
name: 'Get Columns',
|
||||
value: 'getColumns',
|
||||
description: 'Retrieve a list of table columns',
|
||||
action: 'Get columns',
|
||||
},
|
||||
{
|
||||
name: 'Get Rows',
|
||||
value: 'getRows',
|
||||
description: 'Retrieve a list of table rows',
|
||||
action: 'Get rows',
|
||||
},
|
||||
{
|
||||
name: 'Lookup',
|
||||
value: 'lookup',
|
||||
description: 'Look for rows that match a given value in a column',
|
||||
action: 'Lookup a column',
|
||||
},
|
||||
],
|
||||
default: 'append',
|
||||
},
|
||||
...append.description,
|
||||
...addTable.description,
|
||||
...convertToRange.description,
|
||||
...deleteTable.description,
|
||||
...getColumns.description,
|
||||
...getRows.description,
|
||||
...lookup.description,
|
||||
];
|
||||
@@ -0,0 +1,133 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
{
|
||||
displayName: 'Select Range',
|
||||
name: 'selectRange',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Automatically',
|
||||
value: 'auto',
|
||||
description: 'The whole used range on the selected sheet will be converted into a table',
|
||||
},
|
||||
{
|
||||
name: 'Manually',
|
||||
value: 'manual',
|
||||
description: 'Select a range that will be converted into a table',
|
||||
},
|
||||
],
|
||||
default: 'auto',
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'A1:B2',
|
||||
description: 'The range of cells that will be converted to a table',
|
||||
displayOptions: {
|
||||
show: {
|
||||
selectRange: ['manual'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Has Headers',
|
||||
name: 'hasHeaders',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description:
|
||||
'Whether the range has column labels. When this property set to false Excel will automatically generate header shifting the data down by one row.',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['table'],
|
||||
operation: ['addTable'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
//https://learn.microsoft.com/en-us/graph/api/worksheet-post-tables?view=graph-rest-1.0
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const selectRange = this.getNodeParameter('selectRange', i) as string;
|
||||
|
||||
const hasHeaders = this.getNodeParameter('hasHeaders', i) as boolean;
|
||||
|
||||
let range = '';
|
||||
if (selectRange === 'auto') {
|
||||
const { address } = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
|
||||
undefined,
|
||||
{
|
||||
select: 'address',
|
||||
},
|
||||
);
|
||||
range = address.split('!')[1];
|
||||
} else {
|
||||
range = this.getNodeParameter('range', i) as string;
|
||||
}
|
||||
|
||||
const responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/add`,
|
||||
{
|
||||
address: range,
|
||||
hasHeaders,
|
||||
},
|
||||
);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { generatePairedItemData, processJsonInput, updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import type { ExcelResponse } from '../../helpers/interfaces';
|
||||
import { prepareOutput } from '../../helpers/utils';
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { tableRLC, workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
tableRLC,
|
||||
{
|
||||
displayName: 'Data Mode',
|
||||
name: 'dataMode',
|
||||
type: 'options',
|
||||
default: 'define',
|
||||
options: [
|
||||
{
|
||||
name: 'Auto-Map Input Data to Columns',
|
||||
value: 'autoMap',
|
||||
description: 'Use when node input properties match destination column names',
|
||||
},
|
||||
{
|
||||
name: 'Map Each Column Below',
|
||||
value: 'define',
|
||||
description: 'Set the value for each destination column',
|
||||
},
|
||||
{
|
||||
name: 'Raw',
|
||||
value: 'raw',
|
||||
description: 'Send raw data as JSON',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Data',
|
||||
name: 'data',
|
||||
type: 'json',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'e.g. [["Sara","1/2/2006","Berlin"],["George","5/3/2010","Paris"]]',
|
||||
description: 'Raw values for the specified range as array of string arrays in JSON format',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['raw'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Values to Send',
|
||||
name: 'fieldsUi',
|
||||
placeholder: 'Add Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['define'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'values',
|
||||
values: [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Column',
|
||||
name: 'column',
|
||||
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: ['table.value', 'worksheet.value', 'workbook.value'],
|
||||
loadOptionsMethod: 'getTableColumns',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'fieldValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
requiresDataPath: 'single',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
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 row below the inserted row will be shifted downwards. First row index is 0.',
|
||||
},
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-default-wrong-for-boolean
|
||||
default: 0,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['table'],
|
||||
operation: ['append'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
//https://docs.microsoft.com/en-us/graph/api/table-post-rows?view=graph-rest-1.0&tabs=http
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
try {
|
||||
// TODO: At some point it should be possible to use item dependent parameters.
|
||||
// Is however important to then not make one separate request each.
|
||||
const workbookId = this.getNodeParameter('workbook', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const tableId = this.getNodeParameter('table', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const dataMode = this.getNodeParameter('dataMode', 0) as string;
|
||||
|
||||
// Get table columns to eliminate any columns not needed on the input
|
||||
const columnsData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
|
||||
{},
|
||||
);
|
||||
const columnsRow = columnsData.value.map((column: IDataObject) => column.name);
|
||||
|
||||
const body: IDataObject = {};
|
||||
|
||||
let values: string[][] = [];
|
||||
|
||||
if (dataMode === 'raw') {
|
||||
const data = this.getNodeParameter('data', 0);
|
||||
values = processJsonInput(data, 'Data') as string[][];
|
||||
}
|
||||
|
||||
if (dataMode === 'autoMap') {
|
||||
const itemsData = items.map((item) => item.json);
|
||||
for (const item of itemsData) {
|
||||
const updateRow: string[] = [];
|
||||
|
||||
for (const column of columnsRow) {
|
||||
updateRow.push(item[column] as string);
|
||||
}
|
||||
|
||||
values.push(updateRow);
|
||||
}
|
||||
}
|
||||
|
||||
if (dataMode === 'define') {
|
||||
const itemsData: IDataObject[] = [];
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
const updateData: IDataObject = {};
|
||||
const definedFields = this.getNodeParameter('fieldsUi.values', itemIndex, []) as Array<{
|
||||
column: string;
|
||||
fieldValue: string;
|
||||
}>;
|
||||
for (const entry of definedFields) {
|
||||
updateData[entry.column] = entry.fieldValue;
|
||||
}
|
||||
itemsData.push(updateData);
|
||||
}
|
||||
|
||||
for (const item of itemsData) {
|
||||
const updateRow: string[] = [];
|
||||
|
||||
for (const column of columnsRow) {
|
||||
updateRow.push(item[column] as string);
|
||||
}
|
||||
|
||||
values.push(updateRow);
|
||||
}
|
||||
}
|
||||
|
||||
body.values = values;
|
||||
|
||||
const options = this.getNodeParameter('options', 0);
|
||||
|
||||
if (options.index) {
|
||||
body.index = options.index as number;
|
||||
}
|
||||
|
||||
const { id } = await microsoftApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/drive/items/${workbookId}/workbook/createSession`,
|
||||
{ persistChanges: true },
|
||||
);
|
||||
const 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 },
|
||||
);
|
||||
|
||||
const rawData = options.rawData as boolean;
|
||||
const dataProperty = (options.dataProperty as string) || 'data';
|
||||
|
||||
returnData.push(
|
||||
...prepareOutput.call(this, this.getNode(), responseData as ExcelResponse, {
|
||||
columnsRow,
|
||||
dataProperty,
|
||||
rawData,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const itemData = generatePairedItemData(this.getInputData().length);
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { tableRLC, workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [workbookRLC, worksheetRLC, tableRLC];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['table'],
|
||||
operation: ['convertToRange'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const tableId = this.getNodeParameter('table', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/convertToRange`,
|
||||
);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { tableRLC, workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [workbookRLC, worksheetRLC, tableRLC];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['table'],
|
||||
operation: ['deleteTable'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const tableId = this.getNodeParameter('table', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
await microsoftApiRequest.call(
|
||||
this,
|
||||
'DELETE',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}`,
|
||||
);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { microsoftApiRequest, microsoftApiRequestAllItemsSkip } from '../../transport';
|
||||
import { tableRLC, workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
tableRLC,
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
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: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A comma-separated list of the fields to include in the response',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['table'],
|
||||
operation: ['getColumns'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
//https://docs.microsoft.com/en-us/graph/api/table-list-columns?view=graph-rest-1.0&tabs=http
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const qs: IDataObject = {};
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const tableId = this.getNodeParameter('table', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', i);
|
||||
const rawData = this.getNodeParameter('rawData', i);
|
||||
if (rawData) {
|
||||
const filters = this.getNodeParameter('filters', i);
|
||||
if (filters.fields) {
|
||||
qs.$select = filters.fields;
|
||||
}
|
||||
}
|
||||
|
||||
let responseData;
|
||||
if (returnAll) {
|
||||
responseData = await microsoftApiRequestAllItemsSkip.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', i);
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
if (!rawData) {
|
||||
responseData = responseData.map((column: IDataObject) => ({ name: column.name }));
|
||||
} else {
|
||||
const dataProperty = this.getNodeParameter('dataProperty', i) as string;
|
||||
responseData = { [dataProperty]: responseData };
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { microsoftApiRequest, microsoftApiRequestAllItemsSkip } from '../../transport';
|
||||
import { tableRLC, workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
tableRLC,
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
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: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A comma-separated list of the fields to include in the response',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/rawData': [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Column Names or IDs',
|
||||
name: 'column',
|
||||
type: 'multiOptions',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>. Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
|
||||
typeOptions: {
|
||||
loadOptionsDependsOn: ['table.value', 'worksheet.value', 'workbook.value'],
|
||||
loadOptionsMethod: 'getTableColumns',
|
||||
},
|
||||
default: [],
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/rawData': [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['table'],
|
||||
operation: ['getRows'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
//https://docs.microsoft.com/en-us/graph/api/table-list-rows?view=graph-rest-1.0&tabs=http
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const qs: IDataObject = {};
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const tableId = this.getNodeParameter('table', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const filters = this.getNodeParameter('filters', i);
|
||||
const returnAll = this.getNodeParameter('returnAll', i);
|
||||
const rawData = this.getNodeParameter('rawData', i);
|
||||
|
||||
if (rawData) {
|
||||
if (filters.fields) {
|
||||
qs.$select = filters.fields;
|
||||
}
|
||||
}
|
||||
|
||||
let responseData;
|
||||
if (returnAll) {
|
||||
responseData = await microsoftApiRequestAllItemsSkip.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
const rowsQs = { ...qs };
|
||||
rowsQs.$top = this.getNodeParameter('limit', i);
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`,
|
||||
{},
|
||||
rowsQs,
|
||||
);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
if (!rawData) {
|
||||
const columnsQs = { ...qs };
|
||||
columnsQs.$select = 'name';
|
||||
// TODO: That should probably be cached in the future
|
||||
let columns = await microsoftApiRequestAllItemsSkip.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
|
||||
{},
|
||||
columnsQs,
|
||||
);
|
||||
|
||||
columns = (columns as IDataObject[]).map((column) => column.name);
|
||||
|
||||
let rows: INodeExecutionData[] = [];
|
||||
for (let index = 0; index < responseData.length; index++) {
|
||||
const object: IDataObject = {};
|
||||
for (let y = 0; y < columns.length; y++) {
|
||||
object[columns[y]] = responseData[index].values[0][y];
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ ...object }),
|
||||
{ itemData: { item: index } },
|
||||
);
|
||||
|
||||
rows.push(...executionData);
|
||||
}
|
||||
|
||||
if ((filters?.column as string[])?.length) {
|
||||
rows = rows.map((row) => {
|
||||
const rowData: IDataObject = {};
|
||||
Object.keys(row.json).forEach((key) => {
|
||||
if ((filters.column as string[]).includes(key)) {
|
||||
rowData[key] = row.json[key];
|
||||
}
|
||||
});
|
||||
return { ...rowData, json: rowData };
|
||||
});
|
||||
}
|
||||
|
||||
returnData.push(...rows);
|
||||
} else {
|
||||
const dataProperty = this.getNodeParameter('dataProperty', i) as string;
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ [dataProperty]: responseData }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { microsoftApiRequestAllItemsSkip } from '../../transport';
|
||||
import { tableRLC, workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
tableRLC,
|
||||
{
|
||||
displayName: 'Lookup Column',
|
||||
name: 'lookupColumn',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'Email',
|
||||
required: true,
|
||||
description: 'The name of the column in which to look for value',
|
||||
},
|
||||
{
|
||||
displayName: 'Lookup Value',
|
||||
name: 'lookupValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'frank@example.com',
|
||||
required: true,
|
||||
description: 'The value to look for in column',
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Return All Matches',
|
||||
name: 'returnAllMatches',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description:
|
||||
'By default only the first result gets returned. If options gets set all found matches get returned.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['table'],
|
||||
operation: ['lookup'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const qs: IDataObject = {};
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const tableId = this.getNodeParameter('table', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const lookupColumn = this.getNodeParameter('lookupColumn', i) as string;
|
||||
const lookupValue = this.getNodeParameter('lookupValue', i) as string;
|
||||
const options = this.getNodeParameter('options', i);
|
||||
|
||||
let responseData = await microsoftApiRequestAllItemsSkip.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/rows`,
|
||||
{},
|
||||
{},
|
||||
);
|
||||
|
||||
qs.$select = 'name';
|
||||
// TODO: That should probably be cached in the future
|
||||
let columns = await microsoftApiRequestAllItemsSkip.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
columns = columns.map((column: IDataObject) => column.name);
|
||||
|
||||
if (!columns.includes(lookupColumn)) {
|
||||
throw new NodeApiError(this.getNode(), responseData as JsonObject, {
|
||||
message: `Column ${lookupColumn} does not exist on the table selected`,
|
||||
});
|
||||
}
|
||||
|
||||
const result: IDataObject[] = [];
|
||||
|
||||
for (let index = 0; index < responseData.length; index++) {
|
||||
const object: IDataObject = {};
|
||||
for (let y = 0; y < columns.length; y++) {
|
||||
object[columns[y]] = responseData[index].values[0][y];
|
||||
}
|
||||
result.push({ ...object });
|
||||
}
|
||||
|
||||
if (options.returnAllMatches) {
|
||||
responseData = result.filter((data: IDataObject) => {
|
||||
return data[lookupColumn]?.toString() === lookupValue;
|
||||
});
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} else {
|
||||
responseData = result.find((data: IDataObject) => {
|
||||
return data[lookupColumn]?.toString() === lookupValue;
|
||||
});
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||
import { NodeConnectionTypes, type INodeTypeDescription } from 'n8n-workflow';
|
||||
|
||||
import * as table from './table/Table.resource';
|
||||
import * as workbook from './workbook/Workbook.resource';
|
||||
import * as worksheet from './worksheet/Worksheet.resource';
|
||||
|
||||
export const versionDescription: INodeTypeDescription = {
|
||||
displayName: 'Microsoft Excel 365',
|
||||
name: 'microsoftExcel',
|
||||
icon: 'file:excel.svg',
|
||||
group: ['input'],
|
||||
version: [2, 2.1, 2.2],
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Microsoft Excel API',
|
||||
defaults: {
|
||||
name: 'Microsoft Excel 365',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'microsoftExcelOAuth2Api',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName:
|
||||
'This node connects to the Microsoft 365 cloud platform. Use the \'Extract from File\' and \'Convert to File\' nodes to directly manipulate spreadsheet files (.xls, .csv, etc). <a href="https://n8n.io/workflows/890-read-in-an-excel-spreadsheet-file/" target="_blank">More info</a>.',
|
||||
name: 'notice',
|
||||
type: 'notice',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Table',
|
||||
value: 'table',
|
||||
description: 'Represents an Excel table',
|
||||
},
|
||||
{
|
||||
name: 'Workbook',
|
||||
value: 'workbook',
|
||||
description: 'A workbook is the top level object which contains one or more worksheets',
|
||||
},
|
||||
{
|
||||
name: 'Sheet',
|
||||
value: 'worksheet',
|
||||
description: 'A sheet is a grid of cells which can contain data, tables, charts, etc',
|
||||
},
|
||||
],
|
||||
default: 'workbook',
|
||||
},
|
||||
...table.description,
|
||||
...workbook.description,
|
||||
...worksheet.description,
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as addWorksheet from './addWorksheet.operation';
|
||||
import * as deleteWorkbook from './deleteWorkbook.operation';
|
||||
import * as getAll from './getAll.operation';
|
||||
|
||||
export { addWorksheet, deleteWorkbook, getAll };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['workbook'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Add Sheet',
|
||||
value: 'addWorksheet',
|
||||
description: 'Add a new sheet to the workbook',
|
||||
action: 'Add a sheet to a workbook',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'deleteWorkbook',
|
||||
description: 'Delete workbook',
|
||||
action: 'Delete workbook',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get workbooks',
|
||||
action: 'Get workbooks',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
},
|
||||
...addWorksheet.description,
|
||||
...deleteWorkbook.description,
|
||||
...getAll.description,
|
||||
];
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description:
|
||||
'The name of the sheet to be added. The name should be unique. If not specified, Excel will determine the name of the new worksheet.',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['workbook'],
|
||||
operation: ['addWorksheet'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
//https://docs.microsoft.com/en-us/graph/api/worksheetcollection-add?view=graph-rest-1.0&tabs=http
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
const body: IDataObject = {};
|
||||
if (additionalFields.name) {
|
||||
body.name = additionalFields.name;
|
||||
}
|
||||
const { id } = await microsoftApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/drive/items/${workbookId}/workbook/createSession`,
|
||||
{ persistChanges: true },
|
||||
);
|
||||
const 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 (Array.isArray(responseData)) {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} else if (responseData !== undefined) {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [workbookRLC];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['workbook'],
|
||||
operation: ['deleteWorkbook'],
|
||||
},
|
||||
};
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
try {
|
||||
await microsoftApiRequest.call(this, 'DELETE', `/drive/items/${workbookId}`);
|
||||
} catch (error) {
|
||||
if (error?.description.includes('Lock token does not match existing lock')) {
|
||||
const errorDescription =
|
||||
'Lock token does not match existing lock, this error could happen if the file is opened in the browser or the Office client, please close file and try again.';
|
||||
|
||||
throw new NodeOperationError(this.getNode(), error as Error, {
|
||||
itemIndex: i,
|
||||
description: errorDescription,
|
||||
});
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
const responseData = { success: true };
|
||||
|
||||
if (Array.isArray(responseData)) {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} else if (responseData !== undefined) {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { microsoftApiRequest, microsoftApiRequestAllItems } from '../../transport';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
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: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A comma-separated list of the fields to include in the response',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['workbook'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const returnAll = this.getNodeParameter('returnAll', i);
|
||||
const filters = this.getNodeParameter('filters', i);
|
||||
const qs: IDataObject = {};
|
||||
if (filters.fields) {
|
||||
qs.$select = filters.fields;
|
||||
}
|
||||
let responseData;
|
||||
if (returnAll) {
|
||||
responseData = await microsoftApiRequestAllItems.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
"/drive/root/search(q='.xlsx')",
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', i);
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
"/drive/root/search(q='.xlsx')",
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
|
||||
if (Array.isArray(responseData)) {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} else if (responseData !== undefined) {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import * as append from './append.operation';
|
||||
import * as clear from './clear.operation';
|
||||
import * as deleteWorksheet from './deleteWorksheet.operation';
|
||||
import * as getAll from './getAll.operation';
|
||||
import * as readRows from './readRows.operation';
|
||||
import * as update from './update.operation';
|
||||
import * as upsert from './upsert.operation';
|
||||
|
||||
export { append, clear, deleteWorksheet, getAll, readRows, update, upsert };
|
||||
|
||||
export const description: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Append',
|
||||
value: 'append',
|
||||
description: 'Append data to sheet',
|
||||
action: 'Append data to sheet',
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-option-name-wrong-for-upsert
|
||||
name: 'Append or Update',
|
||||
value: 'upsert',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-wrong-for-upsert
|
||||
description: 'Append a new row or update the current one if it already exists (upsert)',
|
||||
action: 'Append or update a sheet',
|
||||
},
|
||||
{
|
||||
name: 'Clear',
|
||||
value: 'clear',
|
||||
description: 'Clear sheet',
|
||||
action: 'Clear sheet',
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'deleteWorksheet',
|
||||
description: 'Delete sheet',
|
||||
action: 'Delete sheet',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Get a list of sheets',
|
||||
action: 'Get sheets',
|
||||
},
|
||||
{
|
||||
name: 'Get Rows',
|
||||
value: 'readRows',
|
||||
description: 'Retrieve a list of sheet rows',
|
||||
action: 'Get rows from sheet',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update rows of a sheet or sheet range',
|
||||
action: 'Update sheet',
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
},
|
||||
...append.description,
|
||||
...clear.description,
|
||||
...deleteWorksheet.description,
|
||||
...getAll.description,
|
||||
...readRows.description,
|
||||
...update.description,
|
||||
...upsert.description,
|
||||
];
|
||||
@@ -0,0 +1,271 @@
|
||||
import {
|
||||
NodeOperationError,
|
||||
type IDataObject,
|
||||
type IExecuteFunctions,
|
||||
type INodeExecutionData,
|
||||
type INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { processJsonInput, updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import type { ExcelResponse } from '../../helpers/interfaces';
|
||||
import { findAppendRange, prepareOutput } from '../../helpers/utils';
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
{
|
||||
displayName: 'Data Mode',
|
||||
name: 'dataMode',
|
||||
type: 'options',
|
||||
default: 'define',
|
||||
options: [
|
||||
{
|
||||
name: 'Auto-Map Input Data to Columns',
|
||||
value: 'autoMap',
|
||||
description: 'Use when node input properties match destination column names',
|
||||
},
|
||||
{
|
||||
name: 'Map Each Column Below',
|
||||
value: 'define',
|
||||
description: 'Set the value for each destination column',
|
||||
},
|
||||
{
|
||||
name: 'Raw',
|
||||
value: 'raw',
|
||||
description: 'Send raw data as JSON',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Data',
|
||||
name: 'data',
|
||||
type: 'json',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'e.g. [["Sara","1/2/2006","Berlin"],["George","5/3/2010","Paris"]]',
|
||||
description: 'Raw values for the specified range as array of string arrays in JSON format',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['raw'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Values to Send',
|
||||
name: 'fieldsUi',
|
||||
placeholder: 'Add Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['define'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'values',
|
||||
values: [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Column',
|
||||
name: 'column',
|
||||
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: ['worksheet.value'],
|
||||
loadOptionsMethod: 'getWorksheetColumnRow',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'fieldValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-default-wrong-for-boolean
|
||||
default: 0,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['append'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
|
||||
const workbookId = this.getNodeParameter('workbook', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const dataMode = this.getNodeParameter('dataMode', 0) as string;
|
||||
|
||||
const worksheetData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
|
||||
);
|
||||
|
||||
let values: string[][] = [];
|
||||
|
||||
if (dataMode === 'raw') {
|
||||
const data = this.getNodeParameter('data', 0);
|
||||
values = processJsonInput(data, 'Data') as string[][];
|
||||
const notArray = !values || !Array.isArray(values);
|
||||
if (notArray) {
|
||||
throw new NodeOperationError(this.getNode(), 'Data must be an array of arrays of strings');
|
||||
}
|
||||
const notStringArray =
|
||||
values.some((item) => !Array.isArray(item)) ||
|
||||
values.flat().some((item) => typeof item !== 'string');
|
||||
if (notStringArray) {
|
||||
throw new NodeOperationError(this.getNode(), 'Data must be an array of arrays of strings');
|
||||
}
|
||||
}
|
||||
|
||||
const isTableEmpty = !worksheetData.address.includes(':');
|
||||
if (isTableEmpty && dataMode !== 'raw') {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'No data found in the specified range, mapping not possible, you can use raw mode instead to update selected range',
|
||||
);
|
||||
}
|
||||
|
||||
const columnsRow = (worksheetData.values as string[][])[0];
|
||||
|
||||
if (dataMode === 'autoMap') {
|
||||
const itemsData = items.map((item) => item.json);
|
||||
for (const item of itemsData) {
|
||||
const updateRow: string[] = [];
|
||||
|
||||
for (const column of columnsRow) {
|
||||
updateRow.push(item[column] as string);
|
||||
}
|
||||
|
||||
values.push(updateRow);
|
||||
}
|
||||
}
|
||||
|
||||
if (dataMode === 'define') {
|
||||
const itemsData: IDataObject[] = [];
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
const updateData: IDataObject = {};
|
||||
const definedFields = this.getNodeParameter('fieldsUi.values', itemIndex, []) as Array<{
|
||||
column: string;
|
||||
fieldValue: string;
|
||||
}>;
|
||||
for (const entry of definedFields) {
|
||||
updateData[entry.column] = entry.fieldValue;
|
||||
}
|
||||
itemsData.push(updateData);
|
||||
}
|
||||
|
||||
for (const item of itemsData) {
|
||||
const updateRow: string[] = [];
|
||||
|
||||
for (const column of columnsRow) {
|
||||
updateRow.push(item[column] as string);
|
||||
}
|
||||
|
||||
values.push(updateRow);
|
||||
}
|
||||
}
|
||||
const { address } = worksheetData;
|
||||
|
||||
let range = '';
|
||||
|
||||
if (nodeVersion >= 2.2) {
|
||||
range = findAppendRange(address, {
|
||||
cols: values[0]?.length ?? 0,
|
||||
rows: values?.length ?? 0,
|
||||
});
|
||||
} else {
|
||||
// v2.1: incorrectly appends raw data, left for backward compatibility reasons
|
||||
// if used range dimensions are smaller or bigger than inserted values dimensions, it will throw error
|
||||
// Example: if used range is 2x4(cols x rows) and new data is 2x3, it will throw error "The number of rows or columns in the input array doesn't match the size or dimensions of the range."
|
||||
const usedRange = address.split('!')[1];
|
||||
|
||||
const [rangeFrom, rangeTo] = usedRange.split(':');
|
||||
const cellDataFrom = rangeFrom.match(/([a-zA-Z]{1,10})([0-9]{0,10})/) || [];
|
||||
const cellDataTo = rangeTo.match(/([a-zA-Z]{1,10})([0-9]{0,10})/) || [];
|
||||
const from = `${cellDataFrom[1]}${Number(cellDataTo[2]) + 1}`;
|
||||
const to = `${cellDataTo[1]}${Number(cellDataTo[2]) + Number(values.length)}`;
|
||||
|
||||
range = `${from}:${to}`;
|
||||
}
|
||||
|
||||
const responseData: ExcelResponse = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{ values },
|
||||
);
|
||||
|
||||
const rawData = this.getNodeParameter('options.rawData', 0, false) as boolean;
|
||||
const dataProperty = this.getNodeParameter('options.dataProperty', 0, 'data') as string;
|
||||
|
||||
returnData.push(
|
||||
...prepareOutput.call(this, this.getNode(), responseData, {
|
||||
columnsRow,
|
||||
dataProperty,
|
||||
rawData,
|
||||
}),
|
||||
);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { INodeExecutionData, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
{
|
||||
displayName: 'Apply To',
|
||||
name: 'applyTo',
|
||||
type: 'options',
|
||||
//values in capital case as required by api
|
||||
options: [
|
||||
{
|
||||
name: 'All',
|
||||
value: 'All',
|
||||
description: 'Clear data in cells and remove all formatting',
|
||||
},
|
||||
{
|
||||
name: 'Formats',
|
||||
value: 'Formats',
|
||||
description: 'Clear formatting(e.g. font size, color) of cells',
|
||||
},
|
||||
{
|
||||
name: 'Contents',
|
||||
value: 'Contents',
|
||||
description: 'Clear data contained in cells',
|
||||
},
|
||||
],
|
||||
default: 'All',
|
||||
},
|
||||
{
|
||||
displayName: 'Select a Range',
|
||||
name: 'useRange',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
placeholder: 'e.g. A1:B2',
|
||||
default: '',
|
||||
description: 'The sheet range that would be cleared, specified using a A1-style notation',
|
||||
hint: 'Leave blank for entire worksheet',
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['clear'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const applyTo = this.getNodeParameter('applyTo', i) as string;
|
||||
const useRange = this.getNodeParameter('useRange', i, false) as boolean;
|
||||
|
||||
if (!useRange) {
|
||||
await microsoftApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range/clear`,
|
||||
{ applyTo },
|
||||
);
|
||||
} else {
|
||||
const range = this.getNodeParameter('range', i, '') as string;
|
||||
await microsoftApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')/clear`,
|
||||
{ applyTo },
|
||||
);
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
import type { INodeExecutionData, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [workbookRLC, worksheetRLC];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['deleteWorksheet'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
await microsoftApiRequest.call(
|
||||
this,
|
||||
'DELETE',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}`,
|
||||
);
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import { microsoftApiRequest, microsoftApiRequestAllItems } from '../../transport';
|
||||
import { workbookRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
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: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Filter',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'A comma-separated list of the fields to include in the response',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
//https://docs.microsoft.com/en-us/graph/api/workbook-list-worksheets?view=graph-rest-1.0&tabs=http
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const qs: IDataObject = {};
|
||||
try {
|
||||
const returnAll = this.getNodeParameter('returnAll', i);
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
const filters = this.getNodeParameter('filters', i);
|
||||
if (filters.fields) {
|
||||
qs.$select = filters.fields;
|
||||
}
|
||||
|
||||
let responseData;
|
||||
if (returnAll) {
|
||||
responseData = await microsoftApiRequestAllItems.call(
|
||||
this,
|
||||
'value',
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
qs.$top = this.getNodeParameter('limit', i);
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
responseData = responseData.value;
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import type { ExcelResponse } from '../../helpers/interfaces';
|
||||
import { checkRange, prepareOutput } from '../../helpers/utils';
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
{
|
||||
displayName: 'Select a Range',
|
||||
name: 'useRange',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. A1:B2',
|
||||
default: '',
|
||||
description:
|
||||
'The sheet range to read the data from specified using a A1-style notation, has to be specific e.g A1:B5, generic ranges like A:B are not supported',
|
||||
hint: 'Leave blank to return entire sheet',
|
||||
displayOptions: {
|
||||
show: {
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Header Row',
|
||||
name: 'keyRow',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
default: 0,
|
||||
hint: 'Index of the row which contains the column names',
|
||||
description: "Relative to selected 'Range', first row index is 0",
|
||||
displayOptions: {
|
||||
show: {
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'First Data Row',
|
||||
name: 'dataStartRow',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
},
|
||||
default: 1,
|
||||
hint: 'Index of first row which contains the actual data',
|
||||
description: "Relative to selected 'Range', first row index is 0",
|
||||
displayOptions: {
|
||||
show: {
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-default-wrong-for-boolean
|
||||
default: 0,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Fields the response will containt. Multiple can be added separated by ,.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['readRows'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
//https://docs.microsoft.com/en-us/graph/api/worksheet-range?view=graph-rest-1.0&tabs=http
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const qs: IDataObject = {};
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', i, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const options = this.getNodeParameter('options', i, {});
|
||||
|
||||
const range = this.getNodeParameter('range', i, '') as string;
|
||||
checkRange(this.getNode(), range);
|
||||
|
||||
const rawData = (options.rawData as boolean) || false;
|
||||
|
||||
if (rawData && options.fields) {
|
||||
qs.$select = options.fields;
|
||||
}
|
||||
|
||||
let responseData;
|
||||
if (range) {
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
} else {
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
}
|
||||
|
||||
if (!rawData) {
|
||||
const keyRow = this.getNodeParameter('keyRow', i, 0) as number;
|
||||
const firstDataRow = this.getNodeParameter('dataStartRow', i, 1) as number;
|
||||
|
||||
returnData.push(
|
||||
...prepareOutput.call(this, this.getNode(), responseData as ExcelResponse, {
|
||||
rawData,
|
||||
keyRow,
|
||||
firstDataRow,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
const dataProperty = (options.dataProperty as string) || 'data';
|
||||
returnData.push(
|
||||
...prepareOutput.call(this, this.getNode(), responseData as ExcelResponse, {
|
||||
rawData,
|
||||
dataProperty,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { generatePairedItemData, processJsonInput, updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import type { ExcelResponse, UpdateSummary } from '../../helpers/interfaces';
|
||||
import {
|
||||
checkRange,
|
||||
prepareOutput,
|
||||
updateByAutoMaping,
|
||||
updateByDefinedValues,
|
||||
} from '../../helpers/utils';
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
{
|
||||
displayName: 'Select a Range',
|
||||
name: 'useRange',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['autoMap', 'define'],
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
placeholder: 'e.g. A1:B2',
|
||||
default: '',
|
||||
description:
|
||||
'The sheet range to read the data from specified using a A1-style notation, has to be specific e.g A1:B5, generic ranges like A:B are not supported. Leave blank to use whole used range in the sheet.',
|
||||
hint: 'First row must contain column names',
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['raw'],
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
placeholder: 'e.g. A1:B2',
|
||||
default: '',
|
||||
description: 'The sheet range to read the data from specified using a A1-style notation',
|
||||
hint: 'Leave blank for entire worksheet',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Mode',
|
||||
name: 'dataMode',
|
||||
type: 'options',
|
||||
default: 'define',
|
||||
options: [
|
||||
{
|
||||
name: 'Auto-Map Input Data to Columns',
|
||||
value: 'autoMap',
|
||||
description: 'Use when node input properties match destination column names',
|
||||
},
|
||||
{
|
||||
name: 'Map Each Column Below',
|
||||
value: 'define',
|
||||
description: 'Set the value for each destination column',
|
||||
},
|
||||
{
|
||||
name: 'Raw',
|
||||
value: 'raw',
|
||||
description:
|
||||
'Send raw data as JSON, the whole selected range would be updated with the new values',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Data',
|
||||
name: 'data',
|
||||
type: 'json',
|
||||
default: '',
|
||||
required: true,
|
||||
placeholder: 'e.g. [["Sara","1/2/2006","Berlin"],["George","5/3/2010","Paris"]]',
|
||||
description:
|
||||
'Raw values for the specified range as array of string arrays in JSON format. Should match the specified range: one array item for each row.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['raw'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased, n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Column to match on',
|
||||
name: 'columnToMatchOn',
|
||||
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: ['worksheet.value', 'workbook.value', 'range'],
|
||||
loadOptionsMethod: 'getWorksheetColumnRow',
|
||||
},
|
||||
default: '',
|
||||
hint: "Used to find the correct row to update. Doesn't get changed.",
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['autoMap', 'define'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Value of Column to Match On',
|
||||
name: 'valueToMatchOn',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['define'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Values to Send',
|
||||
name: 'fieldsUi',
|
||||
placeholder: 'Add Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['define'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'values',
|
||||
values: [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Column',
|
||||
name: 'column',
|
||||
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: ['columnToMatchOn', 'range'],
|
||||
loadOptionsMethod: 'getWorksheetColumnRowSkipColumnToMatchOn',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'fieldValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-default-wrong-for-boolean
|
||||
default: 0,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
{
|
||||
displayName: 'Fields',
|
||||
name: 'fields',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Fields the response will containt. Multiple can be added separated by ,.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Update All Matches',
|
||||
name: 'updateAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to update all matching rows or just the first match',
|
||||
displayOptions: {
|
||||
hide: {
|
||||
'/dataMode': ['raw'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['update'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
try {
|
||||
const options = this.getNodeParameter('options', 0, {});
|
||||
|
||||
const rawData = options.rawData as boolean;
|
||||
const dataProperty = options.dataProperty ? (options.dataProperty as string) : 'data';
|
||||
|
||||
const qs: IDataObject = {};
|
||||
if (rawData && options.fields) {
|
||||
qs.$select = options.fields;
|
||||
}
|
||||
|
||||
const workbookId = this.getNodeParameter('workbook', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let range = this.getNodeParameter('range', 0, '') as string;
|
||||
checkRange(this.getNode(), range);
|
||||
|
||||
const dataMode = this.getNodeParameter('dataMode', 0) as string;
|
||||
|
||||
let worksheetData: IDataObject = {};
|
||||
|
||||
if (range && dataMode !== 'raw') {
|
||||
worksheetData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
);
|
||||
}
|
||||
|
||||
//get used range if range not provided; if 'raw' mode fetch only address information
|
||||
if (range === '') {
|
||||
const query: IDataObject = {};
|
||||
if (dataMode === 'raw') {
|
||||
query.select = 'address';
|
||||
}
|
||||
|
||||
worksheetData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
|
||||
undefined,
|
||||
query,
|
||||
);
|
||||
|
||||
range = (worksheetData.address as string).split('!')[1];
|
||||
}
|
||||
|
||||
let responseData;
|
||||
if (dataMode === 'raw') {
|
||||
const data = this.getNodeParameter('data', 0);
|
||||
|
||||
const values = processJsonInput(data, 'Data') as string[][];
|
||||
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{ values },
|
||||
qs,
|
||||
);
|
||||
|
||||
returnData.push(
|
||||
...prepareOutput.call(this, this.getNode(), responseData as ExcelResponse, {
|
||||
rawData,
|
||||
dataProperty,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
if (worksheetData.values === undefined || (worksheetData.values as string[][]).length <= 1) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'No data found in the specified range, mapping not possible, you can use raw mode instead to update selected range',
|
||||
);
|
||||
}
|
||||
|
||||
const updateAll = this.getNodeParameter('options.updateAll', 0, false) as boolean;
|
||||
|
||||
let updateSummary: UpdateSummary = {
|
||||
updatedData: [],
|
||||
updatedRows: [],
|
||||
appendData: [],
|
||||
};
|
||||
|
||||
if (dataMode === 'define') {
|
||||
updateSummary = updateByDefinedValues.call(
|
||||
this,
|
||||
items.length,
|
||||
worksheetData.values as string[][],
|
||||
updateAll,
|
||||
);
|
||||
}
|
||||
|
||||
if (dataMode === 'autoMap') {
|
||||
const columnToMatchOn = this.getNodeParameter('columnToMatchOn', 0) as string;
|
||||
|
||||
if (!items.some(({ json }) => json[columnToMatchOn] !== undefined)) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Any item in input data contains column '${columnToMatchOn}', that is selected to match on`,
|
||||
);
|
||||
}
|
||||
|
||||
updateSummary = updateByAutoMaping(
|
||||
items,
|
||||
worksheetData.values as string[][],
|
||||
columnToMatchOn,
|
||||
updateAll,
|
||||
);
|
||||
}
|
||||
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{ values: updateSummary.updatedData },
|
||||
);
|
||||
|
||||
const { updatedRows } = updateSummary;
|
||||
|
||||
returnData.push(
|
||||
...prepareOutput.call(this, this.getNode(), responseData as ExcelResponse, {
|
||||
updatedRows,
|
||||
rawData,
|
||||
dataProperty,
|
||||
}),
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const itemData = generatePairedItemData(this.getInputData().length);
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,397 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { generatePairedItemData, processJsonInput, updateDisplayOptions } from '@utils/utilities';
|
||||
|
||||
import type { ExcelResponse, UpdateSummary } from '../../helpers/interfaces';
|
||||
import {
|
||||
checkRange,
|
||||
parseAddress,
|
||||
prepareOutput,
|
||||
updateByAutoMaping,
|
||||
updateByDefinedValues,
|
||||
} from '../../helpers/utils';
|
||||
import { microsoftApiRequest } from '../../transport';
|
||||
import { workbookRLC, worksheetRLC } from '../common.descriptions';
|
||||
|
||||
const properties: INodeProperties[] = [
|
||||
workbookRLC,
|
||||
worksheetRLC,
|
||||
{
|
||||
displayName: 'Select a Range',
|
||||
name: 'useRange',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
{
|
||||
displayName: 'Range',
|
||||
name: 'range',
|
||||
type: 'string',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['autoMap', 'define'],
|
||||
useRange: [true],
|
||||
},
|
||||
},
|
||||
placeholder: 'e.g. A1:B2',
|
||||
default: '',
|
||||
description:
|
||||
'The sheet range to read the data from specified using a A1-style notation, has to be specific e.g A1:B5, generic ranges like A:B are not supported. Leave blank to use whole used range in the sheet.',
|
||||
hint: 'First row must contain column names',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Mode',
|
||||
name: 'dataMode',
|
||||
type: 'options',
|
||||
default: 'define',
|
||||
options: [
|
||||
{
|
||||
name: 'Auto-Map Input Data to Columns',
|
||||
value: 'autoMap',
|
||||
description: 'Use when node input properties match destination column names',
|
||||
},
|
||||
{
|
||||
name: 'Map Each Column Below',
|
||||
value: 'define',
|
||||
description: 'Set the value for each destination column',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased, n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Column to match on',
|
||||
name: 'columnToMatchOn',
|
||||
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: ['worksheet.value', 'workbook.value', 'range'],
|
||||
loadOptionsMethod: 'getWorksheetColumnRow',
|
||||
},
|
||||
default: '',
|
||||
hint: "Used to find the correct row to update. Doesn't get changed.",
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['autoMap', 'define'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Value of Column to Match On',
|
||||
name: 'valueToMatchOn',
|
||||
type: 'string',
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['define'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Values to Send',
|
||||
name: 'fieldsUi',
|
||||
placeholder: 'Add Field',
|
||||
type: 'fixedCollection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
dataMode: ['define'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Field',
|
||||
name: 'values',
|
||||
values: [
|
||||
{
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||
displayName: 'Column',
|
||||
name: 'column',
|
||||
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: ['columnToMatchOn', 'range'],
|
||||
loadOptionsMethod: 'getWorksheetColumnRowSkipColumnToMatchOn',
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Value',
|
||||
name: 'fieldValue',
|
||||
type: 'string',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Options',
|
||||
name: 'options',
|
||||
type: 'collection',
|
||||
placeholder: 'Add option',
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Append After Selected Range',
|
||||
name: 'appendAfterSelectedRange',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to append data after the selected range or used range',
|
||||
displayOptions: {
|
||||
show: {
|
||||
'/dataMode': ['autoMap', 'define'],
|
||||
'/useRange': [true],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'RAW Data',
|
||||
name: 'rawData',
|
||||
type: 'boolean',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-default-wrong-for-boolean
|
||||
default: 0,
|
||||
description:
|
||||
'Whether the data should be returned RAW instead of parsed into keys according to their header',
|
||||
},
|
||||
{
|
||||
displayName: 'Data Property',
|
||||
name: 'dataProperty',
|
||||
type: 'string',
|
||||
default: 'data',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
rawData: [true],
|
||||
},
|
||||
},
|
||||
description: 'The name of the property into which to write the RAW data',
|
||||
},
|
||||
{
|
||||
displayName: 'Update All Matches',
|
||||
name: 'updateAll',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
description: 'Whether to update all matching rows or just the first match',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const displayOptions = {
|
||||
show: {
|
||||
resource: ['worksheet'],
|
||||
operation: ['upsert'],
|
||||
},
|
||||
};
|
||||
|
||||
export const description = updateDisplayOptions(displayOptions, properties);
|
||||
|
||||
export async function execute(
|
||||
this: IExecuteFunctions,
|
||||
items: INodeExecutionData[],
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const nodeVersion = this.getNode().typeVersion;
|
||||
|
||||
try {
|
||||
const workbookId = this.getNodeParameter('workbook', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let range = this.getNodeParameter('range', 0, '') as string;
|
||||
checkRange(this.getNode(), range);
|
||||
|
||||
const dataMode = this.getNodeParameter('dataMode', 0) as string;
|
||||
|
||||
let worksheetData: IDataObject = {};
|
||||
|
||||
if (range && dataMode !== 'raw') {
|
||||
worksheetData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
);
|
||||
}
|
||||
|
||||
//get used range if range not provided; if 'raw' mode fetch only address information
|
||||
if (range === '') {
|
||||
const query: IDataObject = {};
|
||||
if (dataMode === 'raw') {
|
||||
query.select = 'address';
|
||||
}
|
||||
|
||||
worksheetData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
|
||||
undefined,
|
||||
query,
|
||||
);
|
||||
|
||||
range = (worksheetData.address as string).split('!')[1];
|
||||
}
|
||||
|
||||
let responseData;
|
||||
if (dataMode === 'raw') {
|
||||
const data = this.getNodeParameter('data', 0);
|
||||
|
||||
const values = processJsonInput(data, 'Data') as string[][];
|
||||
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{ values },
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
dataMode !== 'raw' &&
|
||||
(worksheetData.values === undefined || (worksheetData.values as string[][]).length < 1)
|
||||
) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'No data found in the specified range, mapping not possible, you can use raw mode instead to update selected range',
|
||||
);
|
||||
}
|
||||
|
||||
const updateAll = this.getNodeParameter('options.updateAll', 0, false) as boolean;
|
||||
|
||||
let updateSummary: UpdateSummary = {
|
||||
updatedData: [],
|
||||
updatedRows: [],
|
||||
appendData: [],
|
||||
};
|
||||
|
||||
if (dataMode === 'define') {
|
||||
updateSummary = updateByDefinedValues.call(
|
||||
this,
|
||||
items.length,
|
||||
worksheetData.values as string[][],
|
||||
updateAll,
|
||||
);
|
||||
}
|
||||
|
||||
if (dataMode === 'autoMap') {
|
||||
const columnToMatchOn = this.getNodeParameter('columnToMatchOn', 0) as string;
|
||||
|
||||
if (!items.some(({ json }) => json[columnToMatchOn] !== undefined)) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
`Any item in input data contains column '${columnToMatchOn}', that is selected to match on`,
|
||||
);
|
||||
}
|
||||
|
||||
updateSummary = updateByAutoMaping(
|
||||
items,
|
||||
worksheetData.values as string[][],
|
||||
columnToMatchOn,
|
||||
updateAll,
|
||||
);
|
||||
}
|
||||
|
||||
const appendAfterSelectedRange = this.getNodeParameter(
|
||||
'options.appendAfterSelectedRange',
|
||||
0,
|
||||
false,
|
||||
) as boolean;
|
||||
|
||||
//remove empty rows from the end
|
||||
if (nodeVersion > 2 && !appendAfterSelectedRange && updateSummary.updatedData.length) {
|
||||
for (let i = updateSummary.updatedData.length - 1; i >= 0; i--) {
|
||||
if (
|
||||
updateSummary.updatedData[i].every(
|
||||
(item) => item === '' || item === undefined || item === null,
|
||||
)
|
||||
) {
|
||||
updateSummary.updatedData.pop();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (updateSummary.appendData.length) {
|
||||
const appendValues: string[][] = [];
|
||||
const columnsRow = (worksheetData.values as string[][])[0];
|
||||
|
||||
for (const [index, item] of updateSummary.appendData.entries()) {
|
||||
const updateRow: string[] = [];
|
||||
|
||||
for (const column of columnsRow) {
|
||||
updateRow.push(item[column] as string);
|
||||
}
|
||||
|
||||
appendValues.push(updateRow);
|
||||
updateSummary.updatedRows.push(index + updateSummary.updatedData.length);
|
||||
}
|
||||
|
||||
updateSummary.updatedData = updateSummary.updatedData.concat(appendValues);
|
||||
const { cellFrom, cellTo } = parseAddress(range);
|
||||
let lastRow = cellTo.row;
|
||||
|
||||
if (nodeVersion > 2 && !appendAfterSelectedRange) {
|
||||
const { address } = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
|
||||
undefined,
|
||||
{ select: 'address' },
|
||||
);
|
||||
|
||||
const usedRange = parseAddress(address as string);
|
||||
|
||||
lastRow = usedRange.cellTo.row;
|
||||
}
|
||||
|
||||
range = `${cellFrom.value}:${cellTo.column}${Number(lastRow) + appendValues.length}`;
|
||||
}
|
||||
|
||||
responseData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{ values: updateSummary.updatedData },
|
||||
);
|
||||
|
||||
const { updatedRows } = updateSummary;
|
||||
|
||||
const rawData = this.getNodeParameter('options.rawData', 0, false) as boolean;
|
||||
const dataProperty = this.getNodeParameter('options.dataProperty', 0, 'data') as string;
|
||||
|
||||
returnData.push(
|
||||
...prepareOutput.call(this, this.getNode(), responseData as ExcelResponse, {
|
||||
updatedRows,
|
||||
rawData,
|
||||
dataProperty,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const itemData = generatePairedItemData(this.getInputData().length);
|
||||
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ error: error.message }),
|
||||
{ itemData },
|
||||
);
|
||||
returnData.push(...executionErrorData);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
|
||||
export type SheetRow = Array<string | number | null>;
|
||||
export type SheetData = SheetRow[];
|
||||
|
||||
export type ExcelResponse = {
|
||||
values: SheetData;
|
||||
};
|
||||
|
||||
export type UpdateSummary = {
|
||||
updatedData: SheetData;
|
||||
appendData: IDataObject[];
|
||||
updatedRows: number[];
|
||||
};
|
||||
@@ -0,0 +1,319 @@
|
||||
import type { IDataObject, IExecuteFunctions, INode, INodeExecutionData } from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { generatePairedItemData, wrapData } from '@utils/utilities';
|
||||
|
||||
import type { ExcelResponse, SheetData, UpdateSummary } from './interfaces';
|
||||
|
||||
export const CELL_REGEX = /([a-zA-Z]{1,10})([0-9]{0,10})/;
|
||||
|
||||
type PrepareOutputConfig = {
|
||||
rawData: boolean;
|
||||
dataProperty?: string;
|
||||
keyRow?: number;
|
||||
firstDataRow?: number;
|
||||
columnsRow?: string[];
|
||||
updatedRows?: number[];
|
||||
};
|
||||
|
||||
export function prepareOutput(
|
||||
this: IExecuteFunctions,
|
||||
node: INode,
|
||||
responseData: ExcelResponse,
|
||||
config: PrepareOutputConfig,
|
||||
) {
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
const { rawData, keyRow, firstDataRow, columnsRow, updatedRows } = {
|
||||
keyRow: 0,
|
||||
firstDataRow: 1,
|
||||
columnsRow: undefined,
|
||||
updatedRows: undefined,
|
||||
...config,
|
||||
};
|
||||
|
||||
if (!rawData) {
|
||||
let values = responseData.values;
|
||||
if (values === null) {
|
||||
throw new NodeOperationError(node, 'Operation did not return data');
|
||||
}
|
||||
|
||||
let columns = [];
|
||||
|
||||
if (columnsRow?.length) {
|
||||
columns = columnsRow;
|
||||
values = [columns, ...values];
|
||||
} else {
|
||||
columns = values[keyRow];
|
||||
}
|
||||
|
||||
if (updatedRows) {
|
||||
values = values.filter((_, index) => updatedRows.includes(index));
|
||||
}
|
||||
|
||||
for (let rowIndex = firstDataRow; rowIndex < values.length; rowIndex++) {
|
||||
if (rowIndex === keyRow) continue;
|
||||
const data: IDataObject = {};
|
||||
for (let columnIndex = 0; columnIndex < columns.length; columnIndex++) {
|
||||
data[columns[columnIndex] as string] = values[rowIndex][columnIndex];
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(wrapData({ ...data }), {
|
||||
itemData: { item: rowIndex },
|
||||
});
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} else {
|
||||
const itemData = generatePairedItemData(this.getInputData().length);
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
wrapData({ [config.dataProperty || 'data']: responseData }),
|
||||
{ itemData },
|
||||
);
|
||||
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
// update values of spreadsheet when update mode is 'define'
|
||||
export function updateByDefinedValues(
|
||||
this: IExecuteFunctions,
|
||||
itemslength: number,
|
||||
sheetData: SheetData,
|
||||
updateAllOccurences: boolean,
|
||||
): UpdateSummary {
|
||||
const [columns, ...originalValues] = sheetData;
|
||||
const updateValues: SheetData = originalValues;
|
||||
|
||||
const updatedRowsIndexes = new Set<number>();
|
||||
const appendData: IDataObject[] = [];
|
||||
|
||||
for (let itemIndex = 0; itemIndex < itemslength; itemIndex++) {
|
||||
const columnToMatchOn = this.getNodeParameter('columnToMatchOn', itemIndex) as string;
|
||||
const valueToMatchOn = this.getNodeParameter('valueToMatchOn', itemIndex) as string;
|
||||
|
||||
const definedFields = this.getNodeParameter('fieldsUi.values', itemIndex, []) as Array<{
|
||||
column: string;
|
||||
fieldValue: string;
|
||||
}>;
|
||||
|
||||
const columnToMatchOnIndex = columns.indexOf(columnToMatchOn);
|
||||
|
||||
const rowIndexes: number[] = [];
|
||||
if (updateAllOccurences) {
|
||||
for (const [index, row] of originalValues.entries()) {
|
||||
if (
|
||||
row[columnToMatchOnIndex] === valueToMatchOn ||
|
||||
Number(row[columnToMatchOnIndex]) === Number(valueToMatchOn)
|
||||
) {
|
||||
rowIndexes.push(index);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const rowIndex = originalValues.findIndex(
|
||||
(row) =>
|
||||
row[columnToMatchOnIndex] === valueToMatchOn ||
|
||||
Number(row[columnToMatchOnIndex]) === Number(valueToMatchOn),
|
||||
);
|
||||
|
||||
if (rowIndex !== -1) {
|
||||
rowIndexes.push(rowIndex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!rowIndexes.length) {
|
||||
const appendItem: IDataObject = {};
|
||||
appendItem[columnToMatchOn] = valueToMatchOn;
|
||||
|
||||
for (const entry of definedFields) {
|
||||
appendItem[entry.column] = entry.fieldValue;
|
||||
}
|
||||
appendData.push(appendItem);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const rowIndex of rowIndexes) {
|
||||
for (const entry of definedFields) {
|
||||
const columnIndex = columns.indexOf(entry.column);
|
||||
if (rowIndex === -1) continue;
|
||||
updateValues[rowIndex][columnIndex] = entry.fieldValue;
|
||||
//add rows index and shift by 1 to account for header row
|
||||
updatedRowsIndexes.add(rowIndex + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updatedData = [columns, ...updateValues];
|
||||
const updatedRows = [0, ...Array.from(updatedRowsIndexes)];
|
||||
|
||||
const summary: UpdateSummary = { updatedData, appendData, updatedRows };
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
// update values of spreadsheet when update mode is 'autoMap'
|
||||
export function updateByAutoMaping(
|
||||
items: INodeExecutionData[],
|
||||
sheetData: SheetData,
|
||||
columnToMatchOn: string,
|
||||
updateAllOccurences = false,
|
||||
): UpdateSummary {
|
||||
const [columns, ...values] = sheetData;
|
||||
const matchColumnIndex = columns.indexOf(columnToMatchOn);
|
||||
const matchValuesMap = values.map((row) => row[matchColumnIndex]);
|
||||
|
||||
const updatedRowsIndexes = new Set<number>();
|
||||
const appendData: IDataObject[] = [];
|
||||
|
||||
for (const { json } of items) {
|
||||
const columnValue = json[columnToMatchOn] as string;
|
||||
if (columnValue === undefined) continue;
|
||||
|
||||
const rowIndexes: number[] = [];
|
||||
if (updateAllOccurences) {
|
||||
matchValuesMap.forEach((value, index) => {
|
||||
if (value === columnValue || Number(value) === Number(columnValue)) {
|
||||
rowIndexes.push(index);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const rowIndex = matchValuesMap.findIndex(
|
||||
(value) => value === columnValue || Number(value) === Number(columnValue),
|
||||
);
|
||||
|
||||
if (rowIndex !== -1) rowIndexes.push(rowIndex);
|
||||
}
|
||||
|
||||
if (!rowIndexes.length) {
|
||||
appendData.push(json);
|
||||
continue;
|
||||
}
|
||||
|
||||
const updatedRow: Array<string | null> = [];
|
||||
|
||||
for (const columnName of columns as string[]) {
|
||||
const updateValue = json[columnName] === undefined ? null : (json[columnName] as string);
|
||||
updatedRow.push(updateValue);
|
||||
}
|
||||
|
||||
for (const rowIndex of rowIndexes) {
|
||||
values[rowIndex] = updatedRow as string[];
|
||||
//add rows index and shift by 1 to account for header row
|
||||
updatedRowsIndexes.add(rowIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
const updatedData = [columns, ...values];
|
||||
const updatedRows = [0, ...Array.from(updatedRowsIndexes)];
|
||||
|
||||
const summary: UpdateSummary = { updatedData, appendData, updatedRows };
|
||||
|
||||
return summary;
|
||||
}
|
||||
|
||||
export const checkRange = (node: INode, range: string) => {
|
||||
const rangeRegex = /^[A-Z]+:[A-Z]+$/i;
|
||||
|
||||
if (rangeRegex.test(range)) {
|
||||
throw new NodeOperationError(
|
||||
node,
|
||||
`Specify the range more precisely e.g. A1:B5, generic ranges like ${range} are not supported`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses strings like A1:B5 to Sheet!A1:B5 into detailed range information
|
||||
* If the range does not have an end, it will be assumed to be the same as the start. E.g. A1 will be parsed as A1:A1
|
||||
*/
|
||||
export const parseAddress = (addressOrRange: string) => {
|
||||
// remove sheet name
|
||||
const range = addressOrRange.replace(/^.+!/, '');
|
||||
|
||||
const [rangeFrom, rangeTo] = range.split(':') as [string, string | undefined];
|
||||
|
||||
const cellFrom = rangeFrom.match(CELL_REGEX) ?? [];
|
||||
|
||||
if (cellFrom.length < 2) {
|
||||
throw new Error(`Failed to parse range: ${addressOrRange}`);
|
||||
}
|
||||
|
||||
const cellTo = (rangeTo ?? rangeFrom)?.match(CELL_REGEX) ?? [];
|
||||
|
||||
if (cellTo.length < 2) {
|
||||
throw new Error(`Failed to parse range: ${addressOrRange}`);
|
||||
}
|
||||
|
||||
return {
|
||||
cellFrom: {
|
||||
value: rangeFrom,
|
||||
column: cellFrom[1],
|
||||
row: cellFrom[2],
|
||||
},
|
||||
cellTo: {
|
||||
value: rangeTo ?? rangeFrom,
|
||||
column: cellTo[1],
|
||||
row: cellTo[2],
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds a next column in the sequence of columns in Excel
|
||||
* Example:
|
||||
* A -> B
|
||||
* Z -> AA
|
||||
*/
|
||||
export const nextExcelColumn = (col: string, offset = 1) => {
|
||||
if (offset < 0) {
|
||||
throw new Error(`Invalid offset: ${offset}`);
|
||||
}
|
||||
|
||||
if (offset === 0) {
|
||||
return col;
|
||||
}
|
||||
|
||||
const toNumber = (s: string) => {
|
||||
return s.split('').reduce((acc, c) => acc * 26 + (c.charCodeAt(0) - 64), 0);
|
||||
};
|
||||
|
||||
const toLetters = (n: number): string => {
|
||||
if (n <= 26) {
|
||||
return String.fromCharCode(64 + n);
|
||||
} else {
|
||||
const rem = ((n - 1) % 26) + 1;
|
||||
const div = Math.floor((n - 1) / 26);
|
||||
return toLetters(div) + String.fromCharCode(64 + rem);
|
||||
}
|
||||
};
|
||||
|
||||
const num = toNumber(col);
|
||||
return toLetters(num + offset);
|
||||
};
|
||||
|
||||
/**
|
||||
* Accepts a used range and finds a new area under the used range.
|
||||
* Changes the new area based on the number of columns and rows inserted.
|
||||
* Example:
|
||||
* A1:B2 -> A3:B4
|
||||
*/
|
||||
export const findAppendRange = (
|
||||
usedRange: string,
|
||||
{ cols, rows }: { cols: number; rows: number },
|
||||
) => {
|
||||
const { cellFrom, cellTo } = parseAddress(usedRange);
|
||||
const isEmptyTable = cellFrom.value === cellTo.value;
|
||||
// if table is empty we don't want to skip the first row
|
||||
const rowOffset = isEmptyTable ? 0 : 1;
|
||||
|
||||
const startingCell = {
|
||||
column: cellFrom.column,
|
||||
row: Number(cellTo.row) + rowOffset,
|
||||
};
|
||||
|
||||
const from = `${startingCell.column}${startingCell.row}`;
|
||||
const nextColumn = nextExcelColumn(startingCell.column, Math.max(cols - 1, 0));
|
||||
const to = `${nextColumn}${Number(startingCell.row) + Math.max(rows - 1, 0)}`;
|
||||
|
||||
return `${from}:${to}`;
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * as loadOptions from './loadOptions';
|
||||
export * as listSearch from './listSearch';
|
||||
@@ -0,0 +1,155 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
ILoadOptionsFunctions,
|
||||
INodeListSearchItems,
|
||||
INodeListSearchResult,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { microsoftApiRequest } from '../transport';
|
||||
|
||||
export async function searchWorkbooks(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
paginationToken?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const fileExtensions = ['.xlsx', '.xlsm', '.xlst'];
|
||||
const extensionFilter = fileExtensions.join(' OR ');
|
||||
|
||||
const q = filter || extensionFilter;
|
||||
|
||||
let response: IDataObject = {};
|
||||
|
||||
if (paginationToken) {
|
||||
response = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'',
|
||||
undefined,
|
||||
undefined,
|
||||
paginationToken, // paginationToken contains the full URL
|
||||
);
|
||||
} else {
|
||||
response = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/root/search(q='${q}')`,
|
||||
undefined,
|
||||
{
|
||||
select: 'id,name,webUrl',
|
||||
$top: 100,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (response.value && filter) {
|
||||
response.value = (response.value as IDataObject[]).filter((workbook: IDataObject) => {
|
||||
return fileExtensions.some((extension) => (workbook.name as string).includes(extension));
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
results: (response.value as IDataObject[]).map((workbook: IDataObject) => {
|
||||
for (const extension of fileExtensions) {
|
||||
if ((workbook.name as string).includes(extension)) {
|
||||
workbook.name = (workbook.name as string).replace(extension, '');
|
||||
break;
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: workbook.name as string,
|
||||
value: workbook.id as string,
|
||||
url: workbook.webUrl as string,
|
||||
};
|
||||
}),
|
||||
paginationToken: response['@odata.nextLink'],
|
||||
};
|
||||
}
|
||||
|
||||
export async function getWorksheetsList(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const workbookRLC = this.getNodeParameter('workbook') as IDataObject;
|
||||
const workbookId = workbookRLC.value as string;
|
||||
let workbookURL = (workbookRLC.cachedResultUrl as string) ?? '';
|
||||
|
||||
if (workbookURL.includes('1drv.ms')) {
|
||||
workbookURL = `https://onedrive.live.com/edit.aspx?resid=${workbookId}`;
|
||||
}
|
||||
|
||||
let response: IDataObject = {};
|
||||
|
||||
response = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets`,
|
||||
undefined,
|
||||
{
|
||||
select: 'id,name',
|
||||
},
|
||||
);
|
||||
|
||||
return {
|
||||
results: (response.value as IDataObject[]).map((worksheet: IDataObject) => ({
|
||||
name: worksheet.name as string,
|
||||
value: worksheet.id as string,
|
||||
url: workbookURL
|
||||
? `${workbookURL}&activeCell=${encodeURIComponent(worksheet.name as string)}!A1`
|
||||
: undefined,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getWorksheetTables(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const workbookRLC = this.getNodeParameter('workbook') as IDataObject;
|
||||
const workbookId = workbookRLC.value as string;
|
||||
let workbookURL = (workbookRLC.cachedResultUrl as string) ?? '';
|
||||
|
||||
if (workbookURL.includes('1drv.ms')) {
|
||||
workbookURL = `https://onedrive.live.com/edit.aspx?resid=${workbookId}`;
|
||||
}
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let response: IDataObject = {};
|
||||
|
||||
response = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables`,
|
||||
undefined,
|
||||
);
|
||||
|
||||
const results: INodeListSearchItems[] = [];
|
||||
|
||||
for (const table of response.value as IDataObject[]) {
|
||||
const name = table.name as string;
|
||||
const value = table.id as string;
|
||||
|
||||
const { address } = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${value}/range`,
|
||||
undefined,
|
||||
{
|
||||
select: 'address',
|
||||
},
|
||||
);
|
||||
|
||||
const [sheetName, sheetRange] = address.split('!' as string);
|
||||
|
||||
let url;
|
||||
if (workbookURL) {
|
||||
url = `${workbookURL}&activeCell=${encodeURIComponent(sheetName as string)}${
|
||||
sheetRange ? '!' + (sheetRange as string) : ''
|
||||
}`;
|
||||
}
|
||||
|
||||
results.push({ name, value, url });
|
||||
}
|
||||
|
||||
return { results };
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
import type { IDataObject, ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
|
||||
|
||||
import { microsoftApiRequest } from '../transport';
|
||||
import { parseAddress } from '../helpers/utils';
|
||||
|
||||
export async function getWorksheetColumnRow(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const workbookId = this.getNodeParameter('workbook', undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
let range = this.getNodeParameter('range', '') as string;
|
||||
let columns: string[] = [];
|
||||
|
||||
if (range === '') {
|
||||
const worksheetData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/usedRange`,
|
||||
undefined,
|
||||
{ select: 'values' },
|
||||
);
|
||||
|
||||
columns = worksheetData.values[0] as string[];
|
||||
} else {
|
||||
const { cellFrom, cellTo } = parseAddress(range);
|
||||
|
||||
range = `${cellFrom.value}:${cellTo.column}${cellFrom.row}`;
|
||||
const worksheetData = await microsoftApiRequest.call(
|
||||
this,
|
||||
'PATCH',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/range(address='${range}')`,
|
||||
{ select: 'values' },
|
||||
);
|
||||
|
||||
columns = worksheetData.values[0] as string[];
|
||||
}
|
||||
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
for (const column of columns) {
|
||||
returnData.push({
|
||||
name: column,
|
||||
value: column,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function getWorksheetColumnRowSkipColumnToMatchOn(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const returnData = await getWorksheetColumnRow.call(this);
|
||||
const columnToMatchOn = this.getNodeParameter('columnToMatchOn', 0) as string;
|
||||
return returnData.filter((column) => column.value !== columnToMatchOn);
|
||||
}
|
||||
|
||||
export async function getTableColumns(
|
||||
this: ILoadOptionsFunctions,
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const workbookId = this.getNodeParameter('workbook', undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const worksheetId = this.getNodeParameter('worksheet', undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const tableId = this.getNodeParameter('table', undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
const response = await microsoftApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/drive/items/${workbookId}/workbook/worksheets/${worksheetId}/tables/${tableId}/columns`,
|
||||
{},
|
||||
);
|
||||
|
||||
return (response.value as IDataObject[]).map((column) => ({
|
||||
name: column.name as string,
|
||||
value: column.name as string,
|
||||
}));
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
import { mockDeep } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions, INode } from 'n8n-workflow';
|
||||
|
||||
import { microsoftApiRequest } from '../../transport/index';
|
||||
|
||||
describe('Microsoft Excel Transport', () => {
|
||||
let mockExecuteFunctions: jest.Mocked<IExecuteFunctions>;
|
||||
let mockNode: INode;
|
||||
let mockRequestOAuth2: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mockExecuteFunctions = mockDeep<IExecuteFunctions>();
|
||||
mockRequestOAuth2 = jest.fn();
|
||||
mockExecuteFunctions.helpers.requestOAuth2 = mockRequestOAuth2;
|
||||
|
||||
mockNode = {
|
||||
id: 'test-node',
|
||||
name: 'Test Excel Node',
|
||||
type: 'n8n-nodes-base.microsoftExcel',
|
||||
typeVersion: 2,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
};
|
||||
mockExecuteFunctions.getNode.mockReturnValue(mockNode);
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
describe('microsoftApiRequest', () => {
|
||||
describe('graphApiBaseUrl from credentials', () => {
|
||||
it('should use base URL from credentials', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.us',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/workbooks');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftExcelOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.us/v1.0/me/workbooks',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should fall back to default when credentials.graphApiBaseUrl is empty', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: '',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/workbooks');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftExcelOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/me/workbooks',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should fall back to default when credentials.graphApiBaseUrl is undefined', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/workbooks');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftExcelOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/me/workbooks',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should strip trailing slashes from base URL using regex', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.com/',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/workbooks');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftExcelOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/me/workbooks',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should strip multiple trailing slashes from base URL', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.com///',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/workbooks');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftExcelOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.com/v1.0/me/workbooks',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use US Government cloud endpoint', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://graph.microsoft.us',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/workbooks');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftExcelOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://graph.microsoft.us/v1.0/me/workbooks',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use US Government DOD cloud endpoint', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://dod-graph.microsoft.us',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/workbooks');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftExcelOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://dod-graph.microsoft.us/v1.0/me/workbooks',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should use China cloud endpoint', async () => {
|
||||
const mockResponse = { data: 'test' };
|
||||
mockRequestOAuth2.mockResolvedValue(mockResponse);
|
||||
mockExecuteFunctions.getCredentials.mockResolvedValue({
|
||||
oauthTokenData: {
|
||||
access_token: 'test-access-token',
|
||||
},
|
||||
graphApiBaseUrl: 'https://microsoftgraph.chinacloudapi.cn',
|
||||
});
|
||||
|
||||
await microsoftApiRequest.call(mockExecuteFunctions, 'GET', '/workbooks');
|
||||
|
||||
expect(mockRequestOAuth2).toHaveBeenCalledWith(
|
||||
'microsoftExcelOAuth2Api',
|
||||
expect.objectContaining({
|
||||
method: 'GET',
|
||||
uri: 'https://microsoftgraph.chinacloudapi.cn/v1.0/me/workbooks',
|
||||
json: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IHttpRequestMethods,
|
||||
ILoadOptionsFunctions,
|
||||
IRequestOptions,
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
export async function microsoftApiRequest(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
resource: string,
|
||||
body: any = {},
|
||||
qs: IDataObject = {},
|
||||
uri?: string,
|
||||
headers: IDataObject = {},
|
||||
): Promise<any> {
|
||||
const credentials = await this.getCredentials('microsoftExcelOAuth2Api');
|
||||
const baseUrl = (
|
||||
typeof credentials.graphApiBaseUrl === 'string' && credentials.graphApiBaseUrl !== ''
|
||||
? credentials.graphApiBaseUrl
|
||||
: 'https://graph.microsoft.com'
|
||||
).replace(/\/+$/, '');
|
||||
const options: IRequestOptions = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs,
|
||||
uri: uri || `${baseUrl}/v1.0/me${resource}`,
|
||||
json: true,
|
||||
};
|
||||
try {
|
||||
if (Object.keys(headers).length !== 0) {
|
||||
options.headers = Object.assign({}, options.headers, headers);
|
||||
}
|
||||
return await this.helpers.requestOAuth2.call(this, 'microsoftExcelOAuth2Api', options);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
export async function microsoftApiRequestAllItems(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
propertyName: string,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
body: any = {},
|
||||
query: IDataObject = {},
|
||||
): Promise<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'];
|
||||
if (uri?.includes('$top')) {
|
||||
delete query.$top;
|
||||
}
|
||||
returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
|
||||
} while (responseData['@odata.nextLink'] !== undefined);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
export async function microsoftApiRequestAllItemsSkip(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
propertyName: string,
|
||||
method: IHttpRequestMethods,
|
||||
endpoint: string,
|
||||
body: any = {},
|
||||
query: IDataObject = {},
|
||||
): Promise<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] as IDataObject[]);
|
||||
} while (responseData.value.length !== 0);
|
||||
|
||||
return returnData;
|
||||
}
|
||||
Reference in New Issue
Block a user