mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-12 21:37:32 -08:00
feat(Microsoft Outlook Node): Node overhaul (#4449)
[N8N-4995](https://linear.app/n8n/issue/N8N-4995) --------- Co-authored-by: Giulio Andreini <g.andreini@gmail.com>
This commit is contained in:
parent
bb215bd12a
commit
556a6132ba
|
@ -1,5 +1,20 @@
|
||||||
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
const scopes = [
|
||||||
|
'openid',
|
||||||
|
'offline_access',
|
||||||
|
'Contacts.Read',
|
||||||
|
'Contacts.ReadWrite',
|
||||||
|
'Calendars.Read',
|
||||||
|
'Calendars.Read.Shared',
|
||||||
|
'Calendars.ReadWrite',
|
||||||
|
'Mail.ReadWrite',
|
||||||
|
'Mail.ReadWrite.Shared',
|
||||||
|
'Mail.Send',
|
||||||
|
'Mail.Send.Shared',
|
||||||
|
'MailboxSettings.Read',
|
||||||
|
];
|
||||||
|
|
||||||
export class MicrosoftOutlookOAuth2Api implements ICredentialType {
|
export class MicrosoftOutlookOAuth2Api implements ICredentialType {
|
||||||
name = 'microsoftOutlookOAuth2Api';
|
name = 'microsoftOutlookOAuth2Api';
|
||||||
|
|
||||||
|
@ -15,8 +30,7 @@ export class MicrosoftOutlookOAuth2Api implements ICredentialType {
|
||||||
displayName: 'Scope',
|
displayName: 'Scope',
|
||||||
name: 'scope',
|
name: 'scope',
|
||||||
type: 'hidden',
|
type: 'hidden',
|
||||||
default:
|
default: scopes.join(' '),
|
||||||
'openid offline_access Mail.ReadWrite Mail.ReadWrite.Shared Mail.Send Mail.Send.Shared MailboxSettings.Read',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
displayName: 'Use Shared Mailbox',
|
displayName: 'Use Shared Mailbox',
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,78 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'POST') {
|
||||||
|
return {
|
||||||
|
'@odata.context':
|
||||||
|
"https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/calendarGroups('AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRu_AAA%3D')/calendars/$entity",
|
||||||
|
id: 'AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAAFXBBZ_AAA=',
|
||||||
|
name: 'New Calendar',
|
||||||
|
color: 'lightOrange',
|
||||||
|
hexColor: '#fcab73',
|
||||||
|
isDefaultCalendar: false,
|
||||||
|
changeKey: 'WX+A3vy5K0qqTyPHso1JgAABVtwWTA==',
|
||||||
|
canShare: true,
|
||||||
|
canViewPrivateItems: true,
|
||||||
|
canEdit: true,
|
||||||
|
allowedOnlineMeetingProviders: ['teamsForBusiness'],
|
||||||
|
defaultOnlineMeetingProvider: 'teamsForBusiness',
|
||||||
|
isTallyingResponses: false,
|
||||||
|
isRemovable: true,
|
||||||
|
owner: {
|
||||||
|
name: 'User Name',
|
||||||
|
address: 'test@mail.com',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, calendar => create', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/calendar/create.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'POST',
|
||||||
|
'/calendarGroups/AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRu_AAA=/calendars',
|
||||||
|
{ color: 'lightOrange', name: 'New Calendar' },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,92 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "calendar",
|
||||||
|
"operation": "create",
|
||||||
|
"name": "New Calendar",
|
||||||
|
"additionalFields": {
|
||||||
|
"calendarGroup": "AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRu_AAA=",
|
||||||
|
"color": "lightOrange"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/calendarGroups('AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRu_AAA%3D')/calendars/$entity",
|
||||||
|
"id": "AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAAFXBBZ_AAA=",
|
||||||
|
"name": "New Calendar",
|
||||||
|
"color": "lightOrange",
|
||||||
|
"hexColor": "#fcab73",
|
||||||
|
"isDefaultCalendar": false,
|
||||||
|
"changeKey": "WX+A3vy5K0qqTyPHso1JgAABVtwWTA==",
|
||||||
|
"canShare": true,
|
||||||
|
"canViewPrivateItems": true,
|
||||||
|
"canEdit": true,
|
||||||
|
"allowedOnlineMeetingProviders": [
|
||||||
|
"teamsForBusiness"
|
||||||
|
],
|
||||||
|
"defaultOnlineMeetingProvider": "teamsForBusiness",
|
||||||
|
"isTallyingResponses": false,
|
||||||
|
"isRemovable": true,
|
||||||
|
"owner": {
|
||||||
|
"name": "User Name",
|
||||||
|
"address": "test@mail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "074cdbef-4193-45b8-970a-9f55b8a0999b",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'DELETE') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, calendar => delete', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/calendar/delete.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'DELETE',
|
||||||
|
'/calendars/AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRvIAAA=',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,74 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "calendar",
|
||||||
|
"operation": "delete",
|
||||||
|
"calendarId": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRvIAAA=",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "Foo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "888e5578-c726-4a84-9658-321ba04fd0c7",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'GET') {
|
||||||
|
return {
|
||||||
|
'@odata.context':
|
||||||
|
"https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/calendars/$entity",
|
||||||
|
id: 'AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRvGAAA=',
|
||||||
|
name: 'Foo Calendar',
|
||||||
|
color: 'lightGreen',
|
||||||
|
hexColor: '#87d28e',
|
||||||
|
isDefaultCalendar: false,
|
||||||
|
changeKey: 'WX+A3vy5K0qqTyPHso1JgAAAi67hiw==',
|
||||||
|
canShare: true,
|
||||||
|
canViewPrivateItems: true,
|
||||||
|
canEdit: true,
|
||||||
|
allowedOnlineMeetingProviders: ['teamsForBusiness'],
|
||||||
|
defaultOnlineMeetingProvider: 'teamsForBusiness',
|
||||||
|
isTallyingResponses: false,
|
||||||
|
isRemovable: true,
|
||||||
|
owner: {
|
||||||
|
name: 'User Name',
|
||||||
|
address: 'test@mail.com',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, calendar => get', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/calendar/get.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'GET',
|
||||||
|
'/calendars/AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRvGAAA=',
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,93 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "calendar",
|
||||||
|
"operation": "get",
|
||||||
|
"calendarId": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRvGAAA=",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "Foo Calendar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/calendars/$entity",
|
||||||
|
"id": "AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRvGAAA=",
|
||||||
|
"name": "Foo Calendar",
|
||||||
|
"color": "lightGreen",
|
||||||
|
"hexColor": "#87d28e",
|
||||||
|
"isDefaultCalendar": false,
|
||||||
|
"changeKey": "WX+A3vy5K0qqTyPHso1JgAAAi67hiw==",
|
||||||
|
"canShare": true,
|
||||||
|
"canViewPrivateItems": true,
|
||||||
|
"canEdit": true,
|
||||||
|
"allowedOnlineMeetingProviders": [
|
||||||
|
"teamsForBusiness"
|
||||||
|
],
|
||||||
|
"defaultOnlineMeetingProvider": "teamsForBusiness",
|
||||||
|
"isTallyingResponses": false,
|
||||||
|
"isRemovable": true,
|
||||||
|
"owner": {
|
||||||
|
"name": "User Name",
|
||||||
|
"address": "test@mail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "33e1fc57-ac36-453b-b01e-d79784b4a4bb",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'GET') {
|
||||||
|
return {
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
id: 'AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAAAJ9-JDAAA=',
|
||||||
|
name: 'Calendar',
|
||||||
|
color: 'auto',
|
||||||
|
hexColor: '',
|
||||||
|
isDefaultCalendar: true,
|
||||||
|
changeKey: 'WX+A3vy5K0qqTyPHso1JgAAACfdHfw==',
|
||||||
|
canShare: true,
|
||||||
|
canViewPrivateItems: true,
|
||||||
|
canEdit: true,
|
||||||
|
allowedOnlineMeetingProviders: ['teamsForBusiness'],
|
||||||
|
defaultOnlineMeetingProvider: 'teamsForBusiness',
|
||||||
|
isTallyingResponses: true,
|
||||||
|
isRemovable: false,
|
||||||
|
owner: {
|
||||||
|
name: 'User Name',
|
||||||
|
address: 'test@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRvBAAA=',
|
||||||
|
name: 'Third calendar',
|
||||||
|
color: 'lightYellow',
|
||||||
|
hexColor: '#fde300',
|
||||||
|
isDefaultCalendar: false,
|
||||||
|
changeKey: 'WX+A3vy5K0qqTyPHso1JgAAAi67hIw==',
|
||||||
|
canShare: true,
|
||||||
|
canViewPrivateItems: true,
|
||||||
|
canEdit: true,
|
||||||
|
allowedOnlineMeetingProviders: ['teamsForBusiness'],
|
||||||
|
defaultOnlineMeetingProvider: 'teamsForBusiness',
|
||||||
|
isTallyingResponses: false,
|
||||||
|
isRemovable: true,
|
||||||
|
owner: {
|
||||||
|
name: 'User Name',
|
||||||
|
address: 'test@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, calendar => getAll', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/calendar/getAll.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith('GET', '/calendars', undefined, {
|
||||||
|
$filter: 'canEdit eq true',
|
||||||
|
$top: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,112 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "calendar",
|
||||||
|
"limit": 2,
|
||||||
|
"filters": {
|
||||||
|
"custom": "canEdit eq true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAAAJ9-JDAAA=",
|
||||||
|
"name": "Calendar",
|
||||||
|
"color": "auto",
|
||||||
|
"hexColor": "",
|
||||||
|
"isDefaultCalendar": true,
|
||||||
|
"changeKey": "WX+A3vy5K0qqTyPHso1JgAAACfdHfw==",
|
||||||
|
"canShare": true,
|
||||||
|
"canViewPrivateItems": true,
|
||||||
|
"canEdit": true,
|
||||||
|
"allowedOnlineMeetingProviders": [
|
||||||
|
"teamsForBusiness"
|
||||||
|
],
|
||||||
|
"defaultOnlineMeetingProvider": "teamsForBusiness",
|
||||||
|
"isTallyingResponses": true,
|
||||||
|
"isRemovable": false,
|
||||||
|
"owner": {
|
||||||
|
"name": "User Name",
|
||||||
|
"address": "test@mail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRvBAAA=",
|
||||||
|
"name": "Third calendar",
|
||||||
|
"color": "lightYellow",
|
||||||
|
"hexColor": "#fde300",
|
||||||
|
"isDefaultCalendar": false,
|
||||||
|
"changeKey": "WX+A3vy5K0qqTyPHso1JgAAAi67hIw==",
|
||||||
|
"canShare": true,
|
||||||
|
"canViewPrivateItems": true,
|
||||||
|
"canEdit": true,
|
||||||
|
"allowedOnlineMeetingProviders": [
|
||||||
|
"teamsForBusiness"
|
||||||
|
],
|
||||||
|
"defaultOnlineMeetingProvider": "teamsForBusiness",
|
||||||
|
"isTallyingResponses": false,
|
||||||
|
"isRemovable": true,
|
||||||
|
"owner": {
|
||||||
|
"name": "User Name",
|
||||||
|
"address": "test@mail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "8f4e7775-0476-4506-a183-1365265b446a",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'PATCH') {
|
||||||
|
return {
|
||||||
|
'@odata.context':
|
||||||
|
"https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/calendars/$entity",
|
||||||
|
id: 'AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRvGAAA=',
|
||||||
|
name: 'Foo',
|
||||||
|
color: 'lightOrange',
|
||||||
|
hexColor: '#fcab73',
|
||||||
|
isDefaultCalendar: false,
|
||||||
|
changeKey: 'WX+A3vy5K0qqTyPHso1JgAABVtwYKA==',
|
||||||
|
canShare: true,
|
||||||
|
canViewPrivateItems: true,
|
||||||
|
canEdit: true,
|
||||||
|
allowedOnlineMeetingProviders: ['teamsForBusiness'],
|
||||||
|
defaultOnlineMeetingProvider: 'teamsForBusiness',
|
||||||
|
isTallyingResponses: false,
|
||||||
|
isRemovable: true,
|
||||||
|
owner: {
|
||||||
|
name: 'User Name',
|
||||||
|
address: 'test@mail.com',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, calendar => update', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/calendar/update.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'PATCH',
|
||||||
|
'/calendars/AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRvGAAA=',
|
||||||
|
{ color: 'lightOrange', isDefaultCalendar: false, name: 'Foo' },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,98 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "calendar",
|
||||||
|
"operation": "update",
|
||||||
|
"calendarId": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRvGAAA=",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "Foo Calendar"
|
||||||
|
},
|
||||||
|
"updateFields": {
|
||||||
|
"color": "lightOrange",
|
||||||
|
"isDefaultCalendar": false,
|
||||||
|
"name": "Foo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/calendars/$entity",
|
||||||
|
"id": "AAAXXXYYYnnnT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAACLtRvGAAA=",
|
||||||
|
"name": "Foo",
|
||||||
|
"color": "lightOrange",
|
||||||
|
"hexColor": "#fcab73",
|
||||||
|
"isDefaultCalendar": false,
|
||||||
|
"changeKey": "WX+A3vy5K0qqTyPHso1JgAABVtwYKA==",
|
||||||
|
"canShare": true,
|
||||||
|
"canViewPrivateItems": true,
|
||||||
|
"canEdit": true,
|
||||||
|
"allowedOnlineMeetingProviders": [
|
||||||
|
"teamsForBusiness"
|
||||||
|
],
|
||||||
|
"defaultOnlineMeetingProvider": "teamsForBusiness",
|
||||||
|
"isTallyingResponses": false,
|
||||||
|
"isRemovable": true,
|
||||||
|
"owner": {
|
||||||
|
"name": "User Name",
|
||||||
|
"address": "test@mail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "f8e68e5f-8c26-4c1f-b11b-8b4e4aeaa61f",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'POST') {
|
||||||
|
return {
|
||||||
|
'@odata.context':
|
||||||
|
"https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/contacts/$entity",
|
||||||
|
'@odata.etag': 'W/"EQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3Bob"',
|
||||||
|
id: 'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEOAABZf4De-LkrSqpPI8eyjUmAAAFXBCQuAAA=',
|
||||||
|
createdDateTime: '2023-09-04T08:48:39Z',
|
||||||
|
lastModifiedDateTime: '2023-09-04T08:48:39Z',
|
||||||
|
changeKey: 'EQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3Bob',
|
||||||
|
categories: ['blue', 'green'],
|
||||||
|
parentFolderId:
|
||||||
|
'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAAAAAEOAAA=',
|
||||||
|
birthday: '1991-09-19T11:59:00Z',
|
||||||
|
fileAs: '',
|
||||||
|
displayName: 'User Name',
|
||||||
|
givenName: 'User',
|
||||||
|
initials: null,
|
||||||
|
middleName: null,
|
||||||
|
nickName: null,
|
||||||
|
surname: 'Name',
|
||||||
|
title: 'Title',
|
||||||
|
yomiGivenName: null,
|
||||||
|
yomiSurname: null,
|
||||||
|
yomiCompanyName: null,
|
||||||
|
generation: null,
|
||||||
|
imAddresses: [],
|
||||||
|
jobTitle: null,
|
||||||
|
companyName: 'Company',
|
||||||
|
department: 'IT',
|
||||||
|
officeLocation: null,
|
||||||
|
profession: null,
|
||||||
|
businessHomePage: null,
|
||||||
|
assistantName: 'Assistant',
|
||||||
|
manager: null,
|
||||||
|
homePhones: [],
|
||||||
|
mobilePhone: null,
|
||||||
|
businessPhones: [],
|
||||||
|
spouseName: null,
|
||||||
|
personalNotes: '',
|
||||||
|
children: [],
|
||||||
|
emailAddresses: [],
|
||||||
|
homeAddress: {},
|
||||||
|
businessAddress: {},
|
||||||
|
otherAddress: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, contact => create', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/contact/create.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith('POST', '/contacts', {
|
||||||
|
assistantName: 'Assistant',
|
||||||
|
birthday: '1991-09-19T21:00:00.000Z',
|
||||||
|
categories: ['blue', 'green'],
|
||||||
|
companyName: 'Company',
|
||||||
|
department: 'IT',
|
||||||
|
displayName: 'User Name',
|
||||||
|
givenName: 'User',
|
||||||
|
surname: 'Name',
|
||||||
|
title: 'Title',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,121 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "contact",
|
||||||
|
"operation": "create",
|
||||||
|
"givenName": "User",
|
||||||
|
"surname": "Name",
|
||||||
|
"additionalFields": {
|
||||||
|
"assistantName": "Assistant",
|
||||||
|
"birthday": "1991-09-19T21:00:00.000Z",
|
||||||
|
"categories": "blue, green",
|
||||||
|
"companyName": "Company",
|
||||||
|
"department": "IT",
|
||||||
|
"displayName": "User Name",
|
||||||
|
"title": "Title"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/contacts/$entity",
|
||||||
|
"@odata.etag": "W/\"EQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3Bob\"",
|
||||||
|
"id": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEOAABZf4De-LkrSqpPI8eyjUmAAAFXBCQuAAA=",
|
||||||
|
"createdDateTime": "2023-09-04T08:48:39Z",
|
||||||
|
"lastModifiedDateTime": "2023-09-04T08:48:39Z",
|
||||||
|
"changeKey": "EQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3Bob",
|
||||||
|
"categories": [
|
||||||
|
"blue",
|
||||||
|
"green"
|
||||||
|
],
|
||||||
|
"parentFolderId": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAAAAAEOAAA=",
|
||||||
|
"birthday": "1991-09-19T11:59:00Z",
|
||||||
|
"fileAs": "",
|
||||||
|
"displayName": "User Name",
|
||||||
|
"givenName": "User",
|
||||||
|
"initials": null,
|
||||||
|
"middleName": null,
|
||||||
|
"nickName": null,
|
||||||
|
"surname": "Name",
|
||||||
|
"title": "Title",
|
||||||
|
"yomiGivenName": null,
|
||||||
|
"yomiSurname": null,
|
||||||
|
"yomiCompanyName": null,
|
||||||
|
"generation": null,
|
||||||
|
"imAddresses": [],
|
||||||
|
"jobTitle": null,
|
||||||
|
"companyName": "Company",
|
||||||
|
"department": "IT",
|
||||||
|
"officeLocation": null,
|
||||||
|
"profession": null,
|
||||||
|
"businessHomePage": null,
|
||||||
|
"assistantName": "Assistant",
|
||||||
|
"manager": null,
|
||||||
|
"homePhones": [],
|
||||||
|
"mobilePhone": null,
|
||||||
|
"businessPhones": [],
|
||||||
|
"spouseName": null,
|
||||||
|
"personalNotes": "",
|
||||||
|
"children": [],
|
||||||
|
"emailAddresses": [],
|
||||||
|
"homeAddress": {},
|
||||||
|
"businessAddress": {},
|
||||||
|
"otherAddress": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "51df88ff-b679-43df-9129-d1b26ea6b82d",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'PATCH') {
|
||||||
|
return {
|
||||||
|
'@odata.context':
|
||||||
|
"https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/contacts/$entity",
|
||||||
|
'@odata.etag': 'W/"EQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3Bou"',
|
||||||
|
id: 'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEOAABZf4De-LkrSqpPI8eyjUmAAAFXBCQuAAA=',
|
||||||
|
createdDateTime: '2023-09-04T08:48:39Z',
|
||||||
|
lastModifiedDateTime: '2023-09-04T09:06:21Z',
|
||||||
|
changeKey: 'EQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3Bou',
|
||||||
|
categories: ['blue', 'green'],
|
||||||
|
parentFolderId:
|
||||||
|
'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAAAAAEOAAA=',
|
||||||
|
birthday: '1991-09-19T11:59:00Z',
|
||||||
|
fileAs: '',
|
||||||
|
displayName: 'Username',
|
||||||
|
givenName: 'User',
|
||||||
|
initials: null,
|
||||||
|
middleName: null,
|
||||||
|
nickName: null,
|
||||||
|
surname: 'Name',
|
||||||
|
title: 'Title',
|
||||||
|
yomiGivenName: null,
|
||||||
|
yomiSurname: null,
|
||||||
|
yomiCompanyName: null,
|
||||||
|
generation: null,
|
||||||
|
imAddresses: [],
|
||||||
|
jobTitle: null,
|
||||||
|
companyName: 'Company',
|
||||||
|
department: 'IT',
|
||||||
|
officeLocation: null,
|
||||||
|
profession: null,
|
||||||
|
businessHomePage: null,
|
||||||
|
assistantName: 'Assistant',
|
||||||
|
manager: 'Manager',
|
||||||
|
homePhones: [],
|
||||||
|
mobilePhone: '',
|
||||||
|
businessPhones: ['999000555777'],
|
||||||
|
spouseName: '',
|
||||||
|
personalNotes: '',
|
||||||
|
children: [],
|
||||||
|
emailAddresses: [],
|
||||||
|
homeAddress: {},
|
||||||
|
businessAddress: {
|
||||||
|
street: 'Street',
|
||||||
|
city: 'City',
|
||||||
|
state: 'State',
|
||||||
|
countryOrRegion: 'Country',
|
||||||
|
postalCode: '777777',
|
||||||
|
},
|
||||||
|
otherAddress: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, contact => update', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/contact/update.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'PATCH',
|
||||||
|
'/contacts/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEOAABZf4De-LkrSqpPI8eyjUmAAAFXBCQuAAA=',
|
||||||
|
{
|
||||||
|
businessAddress: {
|
||||||
|
city: 'City',
|
||||||
|
countryOrRegion: 'Country',
|
||||||
|
postalCode: '777777',
|
||||||
|
state: 'State',
|
||||||
|
street: 'Street',
|
||||||
|
},
|
||||||
|
businessPhones: ['999000555777'],
|
||||||
|
displayName: 'Username',
|
||||||
|
manager: 'Manager',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,138 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "contact",
|
||||||
|
"operation": "update",
|
||||||
|
"contactId": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEOAABZf4De-LkrSqpPI8eyjUmAAAFXBCQuAAA=",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "User Name"
|
||||||
|
},
|
||||||
|
"additionalFields": {
|
||||||
|
"businessAddress": {
|
||||||
|
"values": {
|
||||||
|
"city": "City",
|
||||||
|
"countryOrRegion": "Country",
|
||||||
|
"postalCode": "777777",
|
||||||
|
"state": "State",
|
||||||
|
"street": "Street"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"businessPhones": "999000555777",
|
||||||
|
"displayName": "Username",
|
||||||
|
"manager": "Manager"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/contacts/$entity",
|
||||||
|
"@odata.etag": "W/\"EQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3Bou\"",
|
||||||
|
"id": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEOAABZf4De-LkrSqpPI8eyjUmAAAFXBCQuAAA=",
|
||||||
|
"createdDateTime": "2023-09-04T08:48:39Z",
|
||||||
|
"lastModifiedDateTime": "2023-09-04T09:06:21Z",
|
||||||
|
"changeKey": "EQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3Bou",
|
||||||
|
"categories": [
|
||||||
|
"blue",
|
||||||
|
"green"
|
||||||
|
],
|
||||||
|
"parentFolderId": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAAAAAEOAAA=",
|
||||||
|
"birthday": "1991-09-19T11:59:00Z",
|
||||||
|
"fileAs": "",
|
||||||
|
"displayName": "Username",
|
||||||
|
"givenName": "User",
|
||||||
|
"initials": null,
|
||||||
|
"middleName": null,
|
||||||
|
"nickName": null,
|
||||||
|
"surname": "Name",
|
||||||
|
"title": "Title",
|
||||||
|
"yomiGivenName": null,
|
||||||
|
"yomiSurname": null,
|
||||||
|
"yomiCompanyName": null,
|
||||||
|
"generation": null,
|
||||||
|
"imAddresses": [],
|
||||||
|
"jobTitle": null,
|
||||||
|
"companyName": "Company",
|
||||||
|
"department": "IT",
|
||||||
|
"officeLocation": null,
|
||||||
|
"profession": null,
|
||||||
|
"businessHomePage": null,
|
||||||
|
"assistantName": "Assistant",
|
||||||
|
"manager": "Manager",
|
||||||
|
"homePhones": [],
|
||||||
|
"mobilePhone": "",
|
||||||
|
"businessPhones": [
|
||||||
|
"999000555777"
|
||||||
|
],
|
||||||
|
"spouseName": "",
|
||||||
|
"personalNotes": "",
|
||||||
|
"children": [],
|
||||||
|
"emailAddresses": [],
|
||||||
|
"homeAddress": {},
|
||||||
|
"businessAddress": {
|
||||||
|
"street": "Street",
|
||||||
|
"city": "City",
|
||||||
|
"state": "State",
|
||||||
|
"countryOrRegion": "Country",
|
||||||
|
"postalCode": "777777"
|
||||||
|
},
|
||||||
|
"otherAddress": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "842587ce-f153-49ac-8818-d173d4d6aac6",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'POST') {
|
||||||
|
return {
|
||||||
|
'@odata.context':
|
||||||
|
"https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/messages/$entity",
|
||||||
|
'@odata.etag': 'W/"CQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3Bo2"',
|
||||||
|
id: 'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEPAABZf4De-LkrSqpPI8eyjUmAAAFXBDupAAA=',
|
||||||
|
createdDateTime: '2023-09-04T09:18:35Z',
|
||||||
|
lastModifiedDateTime: '2023-09-04T09:18:35Z',
|
||||||
|
changeKey: 'CQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3Bo2',
|
||||||
|
categories: [
|
||||||
|
'd10cd8f9-14ac-460e-a6ec-c40dd1876ea2',
|
||||||
|
'6844a34e-4d23-4805-9fec-38b7f6e1a780',
|
||||||
|
'fbf44fcd-7689-43a0-99c8-2c9faf6d825a',
|
||||||
|
],
|
||||||
|
receivedDateTime: '2023-09-04T09:18:35Z',
|
||||||
|
sentDateTime: '2023-09-04T09:18:35Z',
|
||||||
|
hasAttachments: false,
|
||||||
|
internetMessageId:
|
||||||
|
'<AM0PR10MB21003DD359041CBE7D64DDB6DDE9A@AM0PR10MB2100.EURPRD10.PROD.OUTLOOK.COM>',
|
||||||
|
subject: 'New Draft',
|
||||||
|
bodyPreview: 'draft message',
|
||||||
|
importance: 'normal',
|
||||||
|
parentFolderId:
|
||||||
|
'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAAAAAEPAAA=',
|
||||||
|
conversationId:
|
||||||
|
'AAQkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAQAKELKTNBg5JJuTnBGaTDyl0=',
|
||||||
|
conversationIndex: 'AQHZ3xDIoQspM0GDkkm5OcEZpMPKXQ==',
|
||||||
|
isDeliveryReceiptRequested: false,
|
||||||
|
isReadReceiptRequested: true,
|
||||||
|
isRead: true,
|
||||||
|
isDraft: true,
|
||||||
|
webLink:
|
||||||
|
'https://outlook.office365.com/owa/?ItemID=AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De%2FLkrSqpPI8eyjUmAAAAAAAEPAABZf4De%2FLkrSqpPI8eyjUmAAAFXBDupAAA%3D&exvsurl=1&viewmodel=ReadMessageItem',
|
||||||
|
inferenceClassification: 'focused',
|
||||||
|
body: {
|
||||||
|
contentType: 'text',
|
||||||
|
content: 'draft message',
|
||||||
|
},
|
||||||
|
toRecipients: [
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
name: 'some@mail.com',
|
||||||
|
address: 'some@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ccRecipients: [],
|
||||||
|
bccRecipients: [
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
name: 'name1@mail.com',
|
||||||
|
address: 'name1@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
name: 'name2@mail.com',
|
||||||
|
address: 'name2@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
replyTo: [
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
name: 'reply@mail.com',
|
||||||
|
address: 'reply@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
flag: {
|
||||||
|
flagStatus: 'notFlagged',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, draft => create', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/draft/create.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'POST',
|
||||||
|
'/messages',
|
||||||
|
{
|
||||||
|
bccRecipients: [
|
||||||
|
{ emailAddress: { address: 'name1@mail.com' } },
|
||||||
|
{ emailAddress: { address: 'name2@mail.com' } },
|
||||||
|
],
|
||||||
|
body: { content: 'draft message', contentType: 'Text' },
|
||||||
|
categories: [
|
||||||
|
'd10cd8f9-14ac-460e-a6ec-c40dd1876ea2',
|
||||||
|
'6844a34e-4d23-4805-9fec-38b7f6e1a780',
|
||||||
|
'fbf44fcd-7689-43a0-99c8-2c9faf6d825a',
|
||||||
|
],
|
||||||
|
importance: 'Normal',
|
||||||
|
internetMessageHeaders: [{ name: 'x-my-header', value: 'header value' }],
|
||||||
|
isReadReceiptRequested: true,
|
||||||
|
replyTo: [{ emailAddress: { address: 'reply@mail.com' } }],
|
||||||
|
subject: 'New Draft',
|
||||||
|
toRecipients: [{ emailAddress: { address: 'some@mail.com' } }],
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,154 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "draft",
|
||||||
|
"subject": "New Draft",
|
||||||
|
"bodyContent": "draft message",
|
||||||
|
"additionalFields": {
|
||||||
|
"bccRecipients": "name1@mail.com, name2@mail.com",
|
||||||
|
"categories": [
|
||||||
|
"d10cd8f9-14ac-460e-a6ec-c40dd1876ea2",
|
||||||
|
"6844a34e-4d23-4805-9fec-38b7f6e1a780",
|
||||||
|
"fbf44fcd-7689-43a0-99c8-2c9faf6d825a"
|
||||||
|
],
|
||||||
|
"internetMessageHeaders": {
|
||||||
|
"headers": [
|
||||||
|
{
|
||||||
|
"name": "x-my-header",
|
||||||
|
"value": "header value"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"importance": "Normal",
|
||||||
|
"bodyContentType": "Text",
|
||||||
|
"isReadReceiptRequested": true,
|
||||||
|
"replyTo": "reply@mail.com",
|
||||||
|
"toRecipients": "some@mail.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/messages/$entity",
|
||||||
|
"@odata.etag": "W/\"CQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3Bo2\"",
|
||||||
|
"id": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEPAABZf4De-LkrSqpPI8eyjUmAAAFXBDupAAA=",
|
||||||
|
"createdDateTime": "2023-09-04T09:18:35Z",
|
||||||
|
"lastModifiedDateTime": "2023-09-04T09:18:35Z",
|
||||||
|
"changeKey": "CQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3Bo2",
|
||||||
|
"categories": [
|
||||||
|
"d10cd8f9-14ac-460e-a6ec-c40dd1876ea2",
|
||||||
|
"6844a34e-4d23-4805-9fec-38b7f6e1a780",
|
||||||
|
"fbf44fcd-7689-43a0-99c8-2c9faf6d825a"
|
||||||
|
],
|
||||||
|
"receivedDateTime": "2023-09-04T09:18:35Z",
|
||||||
|
"sentDateTime": "2023-09-04T09:18:35Z",
|
||||||
|
"hasAttachments": false,
|
||||||
|
"internetMessageId": "<AM0PR10MB21003DD359041CBE7D64DDB6DDE9A@AM0PR10MB2100.EURPRD10.PROD.OUTLOOK.COM>",
|
||||||
|
"subject": "New Draft",
|
||||||
|
"bodyPreview": "draft message",
|
||||||
|
"importance": "normal",
|
||||||
|
"parentFolderId": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAAAAAEPAAA=",
|
||||||
|
"conversationId": "AAQkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAQAKELKTNBg5JJuTnBGaTDyl0=",
|
||||||
|
"conversationIndex": "AQHZ3xDIoQspM0GDkkm5OcEZpMPKXQ==",
|
||||||
|
"isDeliveryReceiptRequested": false,
|
||||||
|
"isReadReceiptRequested": true,
|
||||||
|
"isRead": true,
|
||||||
|
"isDraft": true,
|
||||||
|
"webLink": "https://outlook.office365.com/owa/?ItemID=AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De%2FLkrSqpPI8eyjUmAAAAAAAEPAABZf4De%2FLkrSqpPI8eyjUmAAAFXBDupAAA%3D&exvsurl=1&viewmodel=ReadMessageItem",
|
||||||
|
"inferenceClassification": "focused",
|
||||||
|
"body": {
|
||||||
|
"contentType": "text",
|
||||||
|
"content": "draft message"
|
||||||
|
},
|
||||||
|
"toRecipients": [
|
||||||
|
{
|
||||||
|
"emailAddress": {
|
||||||
|
"name": "some@mail.com",
|
||||||
|
"address": "some@mail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ccRecipients": [],
|
||||||
|
"bccRecipients": [
|
||||||
|
{
|
||||||
|
"emailAddress": {
|
||||||
|
"name": "name1@mail.com",
|
||||||
|
"address": "name1@mail.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"emailAddress": {
|
||||||
|
"name": "name2@mail.com",
|
||||||
|
"address": "name2@mail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"replyTo": [
|
||||||
|
{
|
||||||
|
"emailAddress": {
|
||||||
|
"name": "reply@mail.com",
|
||||||
|
"address": "reply@mail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"flag": {
|
||||||
|
"flagStatus": "notFlagged"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "7c801e19-1108-4bae-85f1-902820df8c5e",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'POST') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, draft => send', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/draft/send.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(2);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'PATCH',
|
||||||
|
'/messages/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEPAABZf4De-LkrSqpPI8eyjUmAAAFXBDupAAA=',
|
||||||
|
{ toRecipients: [{ emailAddress: { address: 'michael.k@radency.com' } }] },
|
||||||
|
);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'POST',
|
||||||
|
'/messages/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEPAABZf4De-LkrSqpPI8eyjUmAAAFXBDupAAA=/send',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "draft",
|
||||||
|
"operation": "send",
|
||||||
|
"draftId": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEPAABZf4De-LkrSqpPI8eyjUmAAAFXBDupAAA=",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "New Draft",
|
||||||
|
"cachedResultUrl": "https://outlook.office365.com/owa/?ItemID=AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De%2FLkrSqpPI8eyjUmAAAAAAAEPAABZf4De%2FLkrSqpPI8eyjUmAAAFXBDupAAA%3D&exvsurl=1&viewmodel=ReadMessageItem"
|
||||||
|
},
|
||||||
|
"to": "michael.k@radency.com"
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "4bbe3403-3da7-4e6e-8341-7da5e8433330",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,155 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'POST') {
|
||||||
|
return {
|
||||||
|
'@odata.context':
|
||||||
|
"https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/calendars('AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAAAJ9-JDAAA%3D')/events/$entity",
|
||||||
|
'@odata.etag': 'W/"WX+A3vy5K0qqTyPHso1JgAABVtwgEQ=="',
|
||||||
|
id: 'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAENAABZf4De-LkrSqpPI8eyjUmAAAFXBFUSAAA=',
|
||||||
|
createdDateTime: '2023-09-04T10:12:47.1985121Z',
|
||||||
|
lastModifiedDateTime: '2023-09-04T10:12:48.2173253Z',
|
||||||
|
changeKey: 'WX+A3vy5K0qqTyPHso1JgAABVtwgEQ==',
|
||||||
|
categories: ['Yellow category', 'Orange category'],
|
||||||
|
transactionId: null,
|
||||||
|
originalStartTimeZone: 'UTC',
|
||||||
|
originalEndTimeZone: 'UTC',
|
||||||
|
iCalUId:
|
||||||
|
'040000008200E00074C5B7101A82E0080000000062DD545A18DFD90100000000000000001000000004C50947C7B42140B29018ABAB42C965',
|
||||||
|
reminderMinutesBeforeStart: 15,
|
||||||
|
isReminderOn: true,
|
||||||
|
hasAttachments: false,
|
||||||
|
subject: 'New Event',
|
||||||
|
bodyPreview:
|
||||||
|
'event description\r\n________________________________________________________________________________\r\nMicrosoft Teams meeting\r\nJoin on your computer, mobile app or room device\r\nClick here to join the meeting\r\nMeeting ID: 355 132 640 047\r\nPasscode: xgUo7v',
|
||||||
|
importance: 'normal',
|
||||||
|
sensitivity: 'personal',
|
||||||
|
isAllDay: false,
|
||||||
|
isCancelled: false,
|
||||||
|
isOrganizer: true,
|
||||||
|
responseRequested: true,
|
||||||
|
seriesMasterId: null,
|
||||||
|
showAs: 'busy',
|
||||||
|
type: 'singleInstance',
|
||||||
|
webLink:
|
||||||
|
'https://outlook.office365.com/owa/?itemid=AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De%2FLkrSqpPI8eyjUmAAAAAAAENAABZf4De%2FLkrSqpPI8eyjUmAAAFXBFUSAAA%3D&exvsurl=1&path=/calendar/item',
|
||||||
|
onlineMeetingUrl: null,
|
||||||
|
isOnlineMeeting: true,
|
||||||
|
onlineMeetingProvider: 'teamsForBusiness',
|
||||||
|
allowNewTimeProposals: true,
|
||||||
|
occurrenceId: null,
|
||||||
|
isDraft: false,
|
||||||
|
hideAttendees: true,
|
||||||
|
responseStatus: {
|
||||||
|
response: 'organizer',
|
||||||
|
time: '0001-01-01T00:00:00Z',
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
contentType: 'html',
|
||||||
|
content:
|
||||||
|
'<html>\r\n<head>\r\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\r\n</head>\r\n<body>\r\nevent description<br>\r\n<div style="width:100%"><span style="white-space:nowrap; color:#5F5F5F; opacity:.36">________________________________________________________________________________</span>\r\n</div>\r\n<div class="me-email-text" lang="en-US" style="color:#252424; font-family:\'Segoe UI\',\'Helvetica Neue\',Helvetica,Arial,sans-serif">\r\n<div style="margin-top:24px; margin-bottom:20px"><span style="font-size:24px; color:#252424">Microsoft Teams meeting</span>\r\n</div>\r\n<div style="margin-bottom:20px">\r\n<div style="margin-top:0px; margin-bottom:0px; font-weight:bold"><span style="font-size:14px; color:#252424">Join on your computer, mobile app or room device</span>\r\n</div>\r\n<a href="https://teams.microsoft.com/l/meetup-join/19%3ameeting_MDZmMzZmYzYtMDc4Yi00NTA2LWE3MTMtZDc5ZDI1M2JmY2M3%40thread.v2/0?context=%7b%22Tid%22%3a%2223786ca6-7ff2-4672-87d0-5c649ee0a337%22%2c%22Oid%22%3a%22b834447b-6848-4af9-8390-d2259ce46b74%22%7d" class="me-email-headline" style="font-size:14px; font-family:\'Segoe UI Semibold\',\'Segoe UI\',\'Helvetica Neue\',Helvetica,Arial,sans-serif; text-decoration:underline; color:#6264a7">Click\r\n here to join the meeting</a> </div>\r\n<div style="margin-bottom:20px; margin-top:20px">\r\n<div style="margin-bottom:4px"><span data-tid="meeting-code" style="font-size:14px; color:#252424">Meeting ID:\r\n<span style="font-size:16px; color:#252424">355 132 640 047</span> </span><br>\r\n<span style="font-size:14px; color:#252424">Passcode: </span><span style="font-size:16px; color:#252424">xgUo7v\r\n</span>\r\n<div style="font-size:14px"><a href="https://www.microsoft.com/en-us/microsoft-teams/download-app" class="me-email-link" style="font-size:14px; text-decoration:underline; color:#6264a7; font-family:\'Segoe UI\',\'Helvetica Neue\',Helvetica,Arial,sans-serif">Download\r\n Teams</a> | <a href="https://www.microsoft.com/microsoft-teams/join-a-meeting" class="me-email-link" style="font-size:14px; text-decoration:underline; color:#6264a7; font-family:\'Segoe UI\',\'Helvetica Neue\',Helvetica,Arial,sans-serif">\r\nJoin on the web</a></div>\r\n</div>\r\n</div>\r\n<div style="margin-bottom:24px; margin-top:20px"><a href="https://aka.ms/JoinTeamsMeeting" class="me-email-link" style="font-size:14px; text-decoration:underline; color:#6264a7; font-family:\'Segoe UI\',\'Helvetica Neue\',Helvetica,Arial,sans-serif">Learn More</a>\r\n | <a href="https://teams.microsoft.com/meetingOptions/?organizerId=b834447b-6848-4af9-8390-d2259ce46b74&tenantId=23786ca6-7ff2-4672-87d0-5c649ee0a337&threadId=19_meeting_MDZmMzZmYzYtMDc4Yi00NTA2LWE3MTMtZDc5ZDI1M2JmY2M3@thread.v2&messageId=0&language=en-US" class="me-email-link" style="font-size:14px; text-decoration:underline; color:#6264a7; font-family:\'Segoe UI\',\'Helvetica Neue\',Helvetica,Arial,sans-serif">\r\nMeeting options</a> </div>\r\n</div>\r\n<div style="font-size:14px; margin-bottom:4px; font-family:\'Segoe UI\',\'Helvetica Neue\',Helvetica,Arial,sans-serif">\r\n</div>\r\n<div style="font-size:12px"></div>\r\n<div></div>\r\n<div style="width:100%"><span style="white-space:nowrap; color:#5F5F5F; opacity:.36">________________________________________________________________________________</span>\r\n</div>\r\n</body>\r\n</html>\r\n',
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
dateTime: '2023-09-05T07:26:47.0000000',
|
||||||
|
timeZone: 'UTC',
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
dateTime: '2023-09-06T07:56:47.0000000',
|
||||||
|
timeZone: 'UTC',
|
||||||
|
},
|
||||||
|
location: {
|
||||||
|
displayName: 'Microsoft Teams Meeting',
|
||||||
|
locationType: 'default',
|
||||||
|
uniqueId: 'Microsoft Teams Meeting',
|
||||||
|
uniqueIdType: 'private',
|
||||||
|
},
|
||||||
|
locations: [
|
||||||
|
{
|
||||||
|
displayName: 'Microsoft Teams Meeting',
|
||||||
|
locationType: 'default',
|
||||||
|
uniqueId: 'Microsoft Teams Meeting',
|
||||||
|
uniqueIdType: 'private',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
recurrence: null,
|
||||||
|
attendees: [],
|
||||||
|
organizer: {
|
||||||
|
emailAddress: {
|
||||||
|
name: 'Michael Kret',
|
||||||
|
address: 'MichaelDevSandbox@5w1hb7.onmicrosoft.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onlineMeeting: {
|
||||||
|
joinUrl:
|
||||||
|
'https://teams.microsoft.com/l/meetup-join/19%3ameeting_MDZmMzZmYzYtMDc4Yi00NTA2LWE3MTMtZDc5ZDI1M2JmY2M3%40thread.v2/0?context=%7b%22Tid%22%3a%2223786ca6-7ff2-4672-87d0-5c649ee0a337%22%2c%22Oid%22%3a%22b834447b-6848-4af9-8390-d2259ce46b74%22%7d',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, contact => event', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/event/create.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'POST',
|
||||||
|
'/calendars/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAAAJ9-JDAAA=/events',
|
||||||
|
{
|
||||||
|
body: { content: 'event description', contentType: 'html' },
|
||||||
|
bodyPreview: 'preview',
|
||||||
|
categories: ['Yellow category', 'Orange category'],
|
||||||
|
end: { dateTime: '2023-09-06T07:56:47.000Z', timeZone: 'UTC' },
|
||||||
|
hideAttendees: true,
|
||||||
|
importance: 'normal',
|
||||||
|
isAllDay: false,
|
||||||
|
isCancelled: false,
|
||||||
|
isDraft: false,
|
||||||
|
isOnlineMeeting: true,
|
||||||
|
sensitivity: 'personal',
|
||||||
|
showAs: 'busy',
|
||||||
|
start: { dateTime: '2023-09-05T07:26:47.000Z', timeZone: 'UTC' },
|
||||||
|
subject: 'New Event',
|
||||||
|
type: 'occurrence',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,170 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "event",
|
||||||
|
"operation": "create",
|
||||||
|
"calendarId": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAAAJ9-JDAAA=",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "Calendar"
|
||||||
|
},
|
||||||
|
"subject": "New Event",
|
||||||
|
"startDateTime": "2023-09-05T07:26:47.000Z",
|
||||||
|
"endDateTime": "2023-09-06T07:56:47.000Z",
|
||||||
|
"additionalFields": {
|
||||||
|
"categories": [
|
||||||
|
"Yellow category",
|
||||||
|
"Orange category"
|
||||||
|
],
|
||||||
|
"body": "event description",
|
||||||
|
"bodyPreview": "preview",
|
||||||
|
"hideAttendees": true,
|
||||||
|
"importance": "normal",
|
||||||
|
"isAllDay": false,
|
||||||
|
"isCancelled": false,
|
||||||
|
"isDraft": false,
|
||||||
|
"isOnlineMeeting": true,
|
||||||
|
"sensitivity": "personal",
|
||||||
|
"showAs": "busy",
|
||||||
|
"type": "occurrence"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/calendars('AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAAAJ9-JDAAA%3D')/events/$entity",
|
||||||
|
"@odata.etag": "W/\"WX+A3vy5K0qqTyPHso1JgAABVtwgEQ==\"",
|
||||||
|
"id": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAENAABZf4De-LkrSqpPI8eyjUmAAAFXBFUSAAA=",
|
||||||
|
"createdDateTime": "2023-09-04T10:12:47.1985121Z",
|
||||||
|
"lastModifiedDateTime": "2023-09-04T10:12:48.2173253Z",
|
||||||
|
"changeKey": "WX+A3vy5K0qqTyPHso1JgAABVtwgEQ==",
|
||||||
|
"categories": [
|
||||||
|
"Yellow category",
|
||||||
|
"Orange category"
|
||||||
|
],
|
||||||
|
"transactionId": null,
|
||||||
|
"originalStartTimeZone": "UTC",
|
||||||
|
"originalEndTimeZone": "UTC",
|
||||||
|
"iCalUId": "040000008200E00074C5B7101A82E0080000000062DD545A18DFD90100000000000000001000000004C50947C7B42140B29018ABAB42C965",
|
||||||
|
"reminderMinutesBeforeStart": 15,
|
||||||
|
"isReminderOn": true,
|
||||||
|
"hasAttachments": false,
|
||||||
|
"subject": "New Event",
|
||||||
|
"bodyPreview": "event description\r\n________________________________________________________________________________\r\nMicrosoft Teams meeting\r\nJoin on your computer, mobile app or room device\r\nClick here to join the meeting\r\nMeeting ID: 355 132 640 047\r\nPasscode: xgUo7v",
|
||||||
|
"importance": "normal",
|
||||||
|
"sensitivity": "personal",
|
||||||
|
"isAllDay": false,
|
||||||
|
"isCancelled": false,
|
||||||
|
"isOrganizer": true,
|
||||||
|
"responseRequested": true,
|
||||||
|
"seriesMasterId": null,
|
||||||
|
"showAs": "busy",
|
||||||
|
"type": "singleInstance",
|
||||||
|
"webLink": "https://outlook.office365.com/owa/?itemid=AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De%2FLkrSqpPI8eyjUmAAAAAAAENAABZf4De%2FLkrSqpPI8eyjUmAAAFXBFUSAAA%3D&exvsurl=1&path=/calendar/item",
|
||||||
|
"onlineMeetingUrl": null,
|
||||||
|
"isOnlineMeeting": true,
|
||||||
|
"onlineMeetingProvider": "teamsForBusiness",
|
||||||
|
"allowNewTimeProposals": true,
|
||||||
|
"occurrenceId": null,
|
||||||
|
"isDraft": false,
|
||||||
|
"hideAttendees": true,
|
||||||
|
"responseStatus": {
|
||||||
|
"response": "organizer",
|
||||||
|
"time": "0001-01-01T00:00:00Z"
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"contentType": "html",
|
||||||
|
"content": "<html>\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\r\n</head>\r\n<body>\r\nevent description<br>\r\n<div style=\"width:100%\"><span style=\"white-space:nowrap; color:#5F5F5F; opacity:.36\">________________________________________________________________________________</span>\r\n</div>\r\n<div class=\"me-email-text\" lang=\"en-US\" style=\"color:#252424; font-family:'Segoe UI','Helvetica Neue',Helvetica,Arial,sans-serif\">\r\n<div style=\"margin-top:24px; margin-bottom:20px\"><span style=\"font-size:24px; color:#252424\">Microsoft Teams meeting</span>\r\n</div>\r\n<div style=\"margin-bottom:20px\">\r\n<div style=\"margin-top:0px; margin-bottom:0px; font-weight:bold\"><span style=\"font-size:14px; color:#252424\">Join on your computer, mobile app or room device</span>\r\n</div>\r\n<a href=\"https://teams.microsoft.com/l/meetup-join/19%3ameeting_MDZmMzZmYzYtMDc4Yi00NTA2LWE3MTMtZDc5ZDI1M2JmY2M3%40thread.v2/0?context=%7b%22Tid%22%3a%2223786ca6-7ff2-4672-87d0-5c649ee0a337%22%2c%22Oid%22%3a%22b834447b-6848-4af9-8390-d2259ce46b74%22%7d\" class=\"me-email-headline\" style=\"font-size:14px; font-family:'Segoe UI Semibold','Segoe UI','Helvetica Neue',Helvetica,Arial,sans-serif; text-decoration:underline; color:#6264a7\">Click\r\n here to join the meeting</a> </div>\r\n<div style=\"margin-bottom:20px; margin-top:20px\">\r\n<div style=\"margin-bottom:4px\"><span data-tid=\"meeting-code\" style=\"font-size:14px; color:#252424\">Meeting ID:\r\n<span style=\"font-size:16px; color:#252424\">355 132 640 047</span> </span><br>\r\n<span style=\"font-size:14px; color:#252424\">Passcode: </span><span style=\"font-size:16px; color:#252424\">xgUo7v\r\n</span>\r\n<div style=\"font-size:14px\"><a href=\"https://www.microsoft.com/en-us/microsoft-teams/download-app\" class=\"me-email-link\" style=\"font-size:14px; text-decoration:underline; color:#6264a7; font-family:'Segoe UI','Helvetica Neue',Helvetica,Arial,sans-serif\">Download\r\n Teams</a> | <a href=\"https://www.microsoft.com/microsoft-teams/join-a-meeting\" class=\"me-email-link\" style=\"font-size:14px; text-decoration:underline; color:#6264a7; font-family:'Segoe UI','Helvetica Neue',Helvetica,Arial,sans-serif\">\r\nJoin on the web</a></div>\r\n</div>\r\n</div>\r\n<div style=\"margin-bottom:24px; margin-top:20px\"><a href=\"https://aka.ms/JoinTeamsMeeting\" class=\"me-email-link\" style=\"font-size:14px; text-decoration:underline; color:#6264a7; font-family:'Segoe UI','Helvetica Neue',Helvetica,Arial,sans-serif\">Learn More</a>\r\n | <a href=\"https://teams.microsoft.com/meetingOptions/?organizerId=b834447b-6848-4af9-8390-d2259ce46b74&tenantId=23786ca6-7ff2-4672-87d0-5c649ee0a337&threadId=19_meeting_MDZmMzZmYzYtMDc4Yi00NTA2LWE3MTMtZDc5ZDI1M2JmY2M3@thread.v2&messageId=0&language=en-US\" class=\"me-email-link\" style=\"font-size:14px; text-decoration:underline; color:#6264a7; font-family:'Segoe UI','Helvetica Neue',Helvetica,Arial,sans-serif\">\r\nMeeting options</a> </div>\r\n</div>\r\n<div style=\"font-size:14px; margin-bottom:4px; font-family:'Segoe UI','Helvetica Neue',Helvetica,Arial,sans-serif\">\r\n</div>\r\n<div style=\"font-size:12px\"></div>\r\n<div></div>\r\n<div style=\"width:100%\"><span style=\"white-space:nowrap; color:#5F5F5F; opacity:.36\">________________________________________________________________________________</span>\r\n</div>\r\n</body>\r\n</html>\r\n"
|
||||||
|
},
|
||||||
|
"start": {
|
||||||
|
"dateTime": "2023-09-05T07:26:47.0000000",
|
||||||
|
"timeZone": "UTC"
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"dateTime": "2023-09-06T07:56:47.0000000",
|
||||||
|
"timeZone": "UTC"
|
||||||
|
},
|
||||||
|
"location": {
|
||||||
|
"displayName": "Microsoft Teams Meeting",
|
||||||
|
"locationType": "default",
|
||||||
|
"uniqueId": "Microsoft Teams Meeting",
|
||||||
|
"uniqueIdType": "private"
|
||||||
|
},
|
||||||
|
"locations": [
|
||||||
|
{
|
||||||
|
"displayName": "Microsoft Teams Meeting",
|
||||||
|
"locationType": "default",
|
||||||
|
"uniqueId": "Microsoft Teams Meeting",
|
||||||
|
"uniqueIdType": "private"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"recurrence": null,
|
||||||
|
"attendees": [],
|
||||||
|
"organizer": {
|
||||||
|
"emailAddress": {
|
||||||
|
"name": "Michael Kret",
|
||||||
|
"address": "MichaelDevSandbox@5w1hb7.onmicrosoft.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"onlineMeeting": {
|
||||||
|
"joinUrl": "https://teams.microsoft.com/l/meetup-join/19%3ameeting_MDZmMzZmYzYtMDc4Yi00NTA2LWE3MTMtZDc5ZDI1M2JmY2M3%40thread.v2/0?context=%7b%22Tid%22%3a%2223786ca6-7ff2-4672-87d0-5c649ee0a337%22%2c%22Oid%22%3a%22b834447b-6848-4af9-8390-d2259ce46b74%22%7d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "dceb08aa-5897-44d1-afbc-748d1e8a2626",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'POST') {
|
||||||
|
return {
|
||||||
|
'@odata.context':
|
||||||
|
"https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/mailFolders('AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAFXBAEGAAA%3D')/childFolders/$entity",
|
||||||
|
id: 'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAFXBAEHAAA=',
|
||||||
|
displayName: 'Folder 42',
|
||||||
|
parentFolderId:
|
||||||
|
'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAFXBAEGAAA=',
|
||||||
|
childFolderCount: 0,
|
||||||
|
unreadItemCount: 0,
|
||||||
|
totalItemCount: 0,
|
||||||
|
sizeInBytes: 0,
|
||||||
|
isHidden: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, contact => folder', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/folder/create.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'POST',
|
||||||
|
'/mailFolders/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAFXBAEGAAA=/childFolders',
|
||||||
|
{ displayName: 'Folder 42' },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,85 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "folder",
|
||||||
|
"displayName": "Folder 42",
|
||||||
|
"options": {
|
||||||
|
"folderId": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAFXBAEGAAA=",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "New folder",
|
||||||
|
"cachedResultUrl": "https://outlook.office365.com/mail/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De%2FLkrSqpPI8eyjUmAAAFXBAEGAAA%3D"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/mailFolders('AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAFXBAEGAAA%3D')/childFolders/$entity",
|
||||||
|
"id": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAFXBAEHAAA=",
|
||||||
|
"displayName": "Folder 42",
|
||||||
|
"parentFolderId": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAFXBAEGAAA=",
|
||||||
|
"childFolderCount": 0,
|
||||||
|
"unreadItemCount": 0,
|
||||||
|
"totalItemCount": 0,
|
||||||
|
"sizeInBytes": 0,
|
||||||
|
"isHidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "6f7a33a8-d48a-4ae2-ab1c-bbe0a83d376f",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequestAllItems: jest.fn(async function (method: string) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'@odata.etag': 'W/"CQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3CAj"',
|
||||||
|
id: 'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAFXBAEHAABZf4De-LkrSqpPI8eyjUmAAAFXBGDUAAA=',
|
||||||
|
subject: 'XXXX',
|
||||||
|
bodyPreview: 'test',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, folderMessage => getAll', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/folderMessage/getAll.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequestAllItems).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequestAllItems).toHaveBeenCalledWith(
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
'/mailFolders/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAFXBAEHAAA=/messages',
|
||||||
|
undefined,
|
||||||
|
{ $select: 'bodyPreview,subject' },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,84 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "folderMessage",
|
||||||
|
"folderId": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAFXBAEHAAA=",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "Folder 42",
|
||||||
|
"cachedResultUrl": "https://outlook.office365.com/mail/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De%2FLkrSqpPI8eyjUmAAAFXBAEHAAA%3D"
|
||||||
|
},
|
||||||
|
"returnAll": true,
|
||||||
|
"output": "fields",
|
||||||
|
"fields": [
|
||||||
|
"bodyPreview",
|
||||||
|
"subject"
|
||||||
|
],
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"@odata.etag": "W/\"CQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3CAj\"",
|
||||||
|
"id": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAFXBAEHAABZf4De-LkrSqpPI8eyjUmAAAFXBGDUAAA=",
|
||||||
|
"subject": "XXXX",
|
||||||
|
"bodyPreview": "test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "63e4c95c-2998-4f3a-bcbc-1dfea015fecb",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'POST') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, message => move', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/message/move.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'POST',
|
||||||
|
'/messages/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEJAABZf4De-LkrSqpPI8eyjUmAAAFXBEVwAAA=/move',
|
||||||
|
{
|
||||||
|
destinationId:
|
||||||
|
'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAFXBAEHAAA=',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,81 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"operation": "move",
|
||||||
|
"messageId": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEJAABZf4De-LkrSqpPI8eyjUmAAAFXBEVwAAA=",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "Hello",
|
||||||
|
"cachedResultUrl": "https://outlook.office365.com/owa/?ItemID=AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De%2FLkrSqpPI8eyjUmAAAAAAAEJAABZf4De%2FLkrSqpPI8eyjUmAAAFXBEVwAAA%3D&exvsurl=1&viewmodel=ReadMessageItem"
|
||||||
|
},
|
||||||
|
"folderId": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAFXBAEHAAA=",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "Folder 42",
|
||||||
|
"cachedResultUrl": "https://outlook.office365.com/mail/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De%2FLkrSqpPI8eyjUmAAAFXBAEHAAA%3D"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "1434b547-7141-4650-8dce-333dda26e092",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,128 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'POST') {
|
||||||
|
return {
|
||||||
|
'@odata.context':
|
||||||
|
"https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/messages/$entity",
|
||||||
|
'@odata.etag': 'W/"CQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3CX+"',
|
||||||
|
id: 'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEPAABZf4De-LkrSqpPI8eyjUmAAAFXBDurAAA=',
|
||||||
|
createdDateTime: '2023-09-04T12:29:59Z',
|
||||||
|
lastModifiedDateTime: '2023-09-04T12:29:59Z',
|
||||||
|
changeKey: 'CQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3CX+',
|
||||||
|
categories: [],
|
||||||
|
receivedDateTime: '2023-09-04T12:29:59Z',
|
||||||
|
sentDateTime: '2023-09-04T12:29:59Z',
|
||||||
|
hasAttachments: false,
|
||||||
|
internetMessageId:
|
||||||
|
'<AM0PR10MB2100903A148F1623165004E3DDE9A@AM0PR10MB2100.EURPRD10.PROD.OUTLOOK.COM>',
|
||||||
|
subject: 'Reply Subject',
|
||||||
|
bodyPreview: 'Reply message',
|
||||||
|
importance: 'high',
|
||||||
|
parentFolderId:
|
||||||
|
'AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAAAAAEPAAA=',
|
||||||
|
conversationId:
|
||||||
|
'AAQkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAQAKwkQLinj69KtoFOxMG2lVY=',
|
||||||
|
conversationIndex: 'AQHZ3yq3rCRAuKePr0q2gU7EwbaVVrAKmLQ4',
|
||||||
|
isDeliveryReceiptRequested: false,
|
||||||
|
isReadReceiptRequested: false,
|
||||||
|
isRead: true,
|
||||||
|
isDraft: true,
|
||||||
|
webLink:
|
||||||
|
'https://outlook.office365.com/owa/?ItemID=AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De%2FLkrSqpPI8eyjUmAAAAAAAEPAABZf4De%2FLkrSqpPI8eyjUmAAAFXBDurAAA%3D&exvsurl=1&viewmodel=ReadMessageItem',
|
||||||
|
inferenceClassification: 'focused',
|
||||||
|
body: {
|
||||||
|
contentType: 'html',
|
||||||
|
content:
|
||||||
|
'<html><head>\r\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></head><body>Reply message </body></html>',
|
||||||
|
},
|
||||||
|
sender: {
|
||||||
|
emailAddress: {
|
||||||
|
name: 'Michael Kret',
|
||||||
|
address: 'MichaelDevSandbox@5w1hb7.onmicrosoft.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
from: {
|
||||||
|
emailAddress: {
|
||||||
|
name: 'Michael Kret',
|
||||||
|
address: 'MichaelDevSandbox@5w1hb7.onmicrosoft.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toRecipients: [
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
name: 'reply@mail.com',
|
||||||
|
address: 'reply@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
ccRecipients: [],
|
||||||
|
bccRecipients: [],
|
||||||
|
replyTo: [],
|
||||||
|
flag: {
|
||||||
|
flagStatus: 'notFlagged',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, message => reply', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/message/reply.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(2);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'POST',
|
||||||
|
'/messages/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEJAABZf4De-LkrSqpPI8eyjUmAAAFXBEVwAAA=/createReply',
|
||||||
|
{
|
||||||
|
message: {
|
||||||
|
body: { content: 'Reply message', contentType: 'html' },
|
||||||
|
importance: 'High',
|
||||||
|
subject: 'Reply Subject',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'POST',
|
||||||
|
'/messages/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEPAABZf4De-LkrSqpPI8eyjUmAAAFXBDurAAA=/send',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,134 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"operation": "reply",
|
||||||
|
"messageId": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEJAABZf4De-LkrSqpPI8eyjUmAAAFXBEVwAAA=",
|
||||||
|
"mode": "list",
|
||||||
|
"cachedResultName": "Hello",
|
||||||
|
"cachedResultUrl": "https://outlook.office365.com/owa/?ItemID=AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De%2FLkrSqpPI8eyjUmAAAAAAAEJAABZf4De%2FLkrSqpPI8eyjUmAAAFXBEVwAAA%3D&exvsurl=1&viewmodel=ReadMessageItem"
|
||||||
|
},
|
||||||
|
"replyToSenderOnly": true,
|
||||||
|
"message": "Reply message",
|
||||||
|
"additionalFields": {
|
||||||
|
"importance": "High",
|
||||||
|
"bodyContentType": "html",
|
||||||
|
"subject": "Reply Subject"
|
||||||
|
},
|
||||||
|
"options": {}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('b834447b-6848-4af9-8390-d2259ce46b74')/messages/$entity",
|
||||||
|
"@odata.etag": "W/\"CQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3CX+\"",
|
||||||
|
"id": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEPAABZf4De-LkrSqpPI8eyjUmAAAFXBDurAAA=",
|
||||||
|
"createdDateTime": "2023-09-04T12:29:59Z",
|
||||||
|
"lastModifiedDateTime": "2023-09-04T12:29:59Z",
|
||||||
|
"changeKey": "CQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFW3CX+",
|
||||||
|
"categories": [],
|
||||||
|
"receivedDateTime": "2023-09-04T12:29:59Z",
|
||||||
|
"sentDateTime": "2023-09-04T12:29:59Z",
|
||||||
|
"hasAttachments": false,
|
||||||
|
"internetMessageId": "<AM0PR10MB2100903A148F1623165004E3DDE9A@AM0PR10MB2100.EURPRD10.PROD.OUTLOOK.COM>",
|
||||||
|
"subject": "Reply Subject",
|
||||||
|
"bodyPreview": "Reply message",
|
||||||
|
"importance": "high",
|
||||||
|
"parentFolderId": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAuAAAAAABPLqzvT6b9RLP0CKzHiJrRAQBZf4De-LkrSqpPI8eyjUmAAAAAAAEPAAA=",
|
||||||
|
"conversationId": "AAQkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OAAQAKwkQLinj69KtoFOxMG2lVY=",
|
||||||
|
"conversationIndex": "AQHZ3yq3rCRAuKePr0q2gU7EwbaVVrAKmLQ4",
|
||||||
|
"isDeliveryReceiptRequested": false,
|
||||||
|
"isReadReceiptRequested": false,
|
||||||
|
"isRead": true,
|
||||||
|
"isDraft": true,
|
||||||
|
"webLink": "https://outlook.office365.com/owa/?ItemID=AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De%2FLkrSqpPI8eyjUmAAAAAAAEPAABZf4De%2FLkrSqpPI8eyjUmAAAFXBDurAAA%3D&exvsurl=1&viewmodel=ReadMessageItem",
|
||||||
|
"inferenceClassification": "focused",
|
||||||
|
"body": {
|
||||||
|
"contentType": "html",
|
||||||
|
"content": "<html><head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head><body>Reply message </body></html>"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"emailAddress": {
|
||||||
|
"name": "Michael Kret",
|
||||||
|
"address": "MichaelDevSandbox@5w1hb7.onmicrosoft.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"from": {
|
||||||
|
"emailAddress": {
|
||||||
|
"name": "Michael Kret",
|
||||||
|
"address": "MichaelDevSandbox@5w1hb7.onmicrosoft.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toRecipients": [
|
||||||
|
{
|
||||||
|
"emailAddress": {
|
||||||
|
"name": "reply@mail.com",
|
||||||
|
"address": "reply@mail.com"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"ccRecipients": [],
|
||||||
|
"bccRecipients": [],
|
||||||
|
"replyTo": [],
|
||||||
|
"flag": {
|
||||||
|
"flagStatus": "notFlagged"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "edd34dd1-0321-4d5b-a935-0c47d4c746b8",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
import type { INodeTypes } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import nock from 'nock';
|
||||||
|
import * as transport from '../../../../v2/transport';
|
||||||
|
import { getResultNodeData, setup, workflowToTests } from '@test/nodes/Helpers';
|
||||||
|
import type { WorkflowTestData } from '@test/nodes/types';
|
||||||
|
import { executeWorkflow } from '@test/nodes/ExecuteWorkflow';
|
||||||
|
|
||||||
|
jest.mock('../../../../v2/transport', () => {
|
||||||
|
const originalModule = jest.requireActual('../../../../v2/transport');
|
||||||
|
return {
|
||||||
|
...originalModule,
|
||||||
|
microsoftApiRequest: jest.fn(async function (method: string) {
|
||||||
|
if (method === 'POST') {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, message => send', () => {
|
||||||
|
const workflows = ['nodes/Microsoft/Outlook/test/v2/node/message/send.workflow.json'];
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
jest.unmock('../../../../v2/transport');
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
const testNode = async (testData: WorkflowTestData, types: INodeTypes) => {
|
||||||
|
const { result } = await executeWorkflow(testData, types);
|
||||||
|
|
||||||
|
const resultNodeData = getResultNodeData(result, testData);
|
||||||
|
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) => {
|
||||||
|
return expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledTimes(1);
|
||||||
|
expect(transport.microsoftApiRequest).toHaveBeenCalledWith(
|
||||||
|
'POST',
|
||||||
|
'/sendMail',
|
||||||
|
{
|
||||||
|
message: {
|
||||||
|
body: { content: 'message description', contentType: 'Text' },
|
||||||
|
replyTo: [{ emailAddress: { address: 'reply@mail.com' } }],
|
||||||
|
subject: 'Hello',
|
||||||
|
toRecipients: [{ emailAddress: { address: 'to@mail.com' } }],
|
||||||
|
},
|
||||||
|
saveToSentItems: true,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => testNode(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"name": "My workflow 21",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "e524f588-b6a3-4849-8777-b32a8a755ae5",
|
||||||
|
"name": "When clicking \"Execute Workflow\"",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
360
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"toRecipients": "to@mail.com",
|
||||||
|
"subject": "Hello",
|
||||||
|
"bodyContent": "message description",
|
||||||
|
"additionalFields": {
|
||||||
|
"bodyContentType": "Text",
|
||||||
|
"replyTo": "reply@mail.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
|
||||||
|
"name": "Microsoft Outlook",
|
||||||
|
"type": "n8n-nodes-base.microsoftOutlook",
|
||||||
|
"typeVersion": 2,
|
||||||
|
"position": [
|
||||||
|
1040,
|
||||||
|
360
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"microsoftOutlookOAuth2Api": {
|
||||||
|
"id": "iXJCki7i5Vz0bdks",
|
||||||
|
"name": "Microsoft Outlook account 2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"Microsoft Outlook": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking \"Execute Workflow\"": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Microsoft Outlook",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "fed8ec2e-8663-4dfc-8234-33eb83257260",
|
||||||
|
"id": "1CYHzBXQw1nfPGtB",
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
|
||||||
|
},
|
||||||
|
"tags": []
|
||||||
|
}
|
|
@ -0,0 +1,231 @@
|
||||||
|
import {
|
||||||
|
createMessage,
|
||||||
|
makeRecipient,
|
||||||
|
prepareContactFields,
|
||||||
|
prepareFilterString,
|
||||||
|
simplifyOutputMessages,
|
||||||
|
} from '../../../v2/helpers/utils';
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, makeRecipient', () => {
|
||||||
|
it('should create recipient object', () => {
|
||||||
|
const replyTo = 'test1@mail.com, test2@mail.com';
|
||||||
|
const result = replyTo.split(',').map((recipient: string) => {
|
||||||
|
return makeRecipient(recipient.trim());
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
address: 'test1@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
address: 'test2@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, prepareContactFields', () => {
|
||||||
|
it('should create contact object', () => {
|
||||||
|
const fields = {
|
||||||
|
assistantName: 'Assistant',
|
||||||
|
birthday: '2023-07-31T21:00:00.000Z',
|
||||||
|
businessAddress: {
|
||||||
|
values: {
|
||||||
|
city: 'City',
|
||||||
|
countryOrRegion: 'Country',
|
||||||
|
postalCode: '777777',
|
||||||
|
state: 'State',
|
||||||
|
street: 'Street',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
businessHomePage: 'page.com',
|
||||||
|
categories: 'cat1,cat2',
|
||||||
|
companyName: 'Company',
|
||||||
|
};
|
||||||
|
const result = {
|
||||||
|
assistantName: 'Assistant',
|
||||||
|
birthday: '2023-07-31T21:00:00.000Z',
|
||||||
|
businessAddress: {
|
||||||
|
city: 'City',
|
||||||
|
countryOrRegion: 'Country',
|
||||||
|
postalCode: '777777',
|
||||||
|
state: 'State',
|
||||||
|
street: 'Street',
|
||||||
|
},
|
||||||
|
businessHomePage: 'page.com',
|
||||||
|
categories: ['cat1', 'cat2'],
|
||||||
|
companyName: 'Company',
|
||||||
|
};
|
||||||
|
|
||||||
|
const data = prepareContactFields(fields);
|
||||||
|
|
||||||
|
expect(data).toEqual(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, prepareFilterString', () => {
|
||||||
|
it('should create filter string', () => {
|
||||||
|
const filters = {
|
||||||
|
filterBy: 'filters',
|
||||||
|
filters: {
|
||||||
|
custom: 'isRead eq false',
|
||||||
|
hasAttachments: true,
|
||||||
|
foldersToExclude: ['AAAxBBB...='],
|
||||||
|
foldersToInclude: ['DDDxCCC...='],
|
||||||
|
readStatus: 'unread',
|
||||||
|
receivedAfter: '2023-07-31T21:00:00.000Z',
|
||||||
|
receivedBefore: '2023-08-14T21:00:00.000Z',
|
||||||
|
sender: 'test@mail.com',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result =
|
||||||
|
"parentFolderId eq 'DDDxCCC...=' and parentFolderId ne 'AAAxBBB...=' and (from/emailAddress/address eq 'test@mail.com' or from/emailAddress/name eq 'test@mail.com') and hasAttachments eq true and isRead eq false and receivedDateTime ge 2023-07-31T21:00:00.000Z and receivedDateTime le 2023-08-14T21:00:00.000Z and isRead eq false";
|
||||||
|
|
||||||
|
const data = prepareFilterString(filters);
|
||||||
|
|
||||||
|
expect(data).toEqual(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, simplifyOutputMessages', () => {
|
||||||
|
it('should create recipient object', () => {
|
||||||
|
const responseData = {
|
||||||
|
'@odata.context':
|
||||||
|
"https://graph.microsoft.com/v1.0/$metadata#users('')/messages(id,conversationId,subject,bodyPreview,from,toRecipients,categories,hasAttachments)/$entity",
|
||||||
|
'@odata.etag': 'W/"CQAAABYAAABZf4De/LkrSqpPI8eyjUmAAAFSpKec"',
|
||||||
|
id: 'AAAxBBBxCCC...=',
|
||||||
|
categories: [],
|
||||||
|
hasAttachments: false,
|
||||||
|
subject: 'My draft',
|
||||||
|
bodyPreview:
|
||||||
|
'test\r\n________________________________\r\nFrom: Me\r\nSent: Tuesday, August 29, 2023 7:33:28 AM\r\nTo: from@mail.com <from@mail.com>\r\nSubject: My draft\r\n\r\nthis is a draft',
|
||||||
|
conversationId: 'AAAQQQMMM..=',
|
||||||
|
from: {
|
||||||
|
emailAddress: {
|
||||||
|
name: 'Me',
|
||||||
|
address: 'test@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
toRecipients: [
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
name: 'Me',
|
||||||
|
address: 'test@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const result = [
|
||||||
|
{
|
||||||
|
id: 'AAAxBBBxCCC...=',
|
||||||
|
conversationId: 'AAAQQQMMM..=',
|
||||||
|
subject: 'My draft',
|
||||||
|
bodyPreview:
|
||||||
|
'test\r\n________________________________\r\nFrom: Me\r\nSent: Tuesday, August 29, 2023 7:33:28 AM\r\nTo: from@mail.com <from@mail.com>\r\nSubject: My draft\r\n\r\nthis is a draft',
|
||||||
|
from: 'test@mail.com',
|
||||||
|
to: ['test@mail.com'],
|
||||||
|
categories: [],
|
||||||
|
hasAttachments: false,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const data = simplifyOutputMessages([responseData]);
|
||||||
|
|
||||||
|
expect(data).toEqual(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test MicrosoftOutlookV2, createMessage', () => {
|
||||||
|
it('should create message object', () => {
|
||||||
|
const fields = {
|
||||||
|
bodyContent: 'Test message',
|
||||||
|
bodyContentType: 'Text',
|
||||||
|
bccRecipients: 'test1@mail.com, test2@mail.com',
|
||||||
|
categories: ['cat1', 'cat2', 'cat3'],
|
||||||
|
ccRecipients: 'test3@mail.com',
|
||||||
|
internetMessageHeaders: [
|
||||||
|
{
|
||||||
|
name: 'customHeader',
|
||||||
|
value: 'customValue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customHeader2',
|
||||||
|
value: 'customValue2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
from: 'me@mail.com',
|
||||||
|
importance: 'Normal',
|
||||||
|
isReadReceiptRequested: true,
|
||||||
|
replyTo: 'test4@mail.com',
|
||||||
|
subject: 'Test Subject',
|
||||||
|
toRecipients: 'to@mail.com',
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
body: {
|
||||||
|
content: 'Test message',
|
||||||
|
contentType: 'Text',
|
||||||
|
},
|
||||||
|
bccRecipients: [
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
address: 'test1@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
address: 'test2@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
categories: ['cat1', 'cat2', 'cat3'],
|
||||||
|
ccRecipients: [
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
address: 'test3@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
internetMessageHeaders: [
|
||||||
|
{
|
||||||
|
name: 'customHeader',
|
||||||
|
value: 'customValue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'customHeader2',
|
||||||
|
value: 'customValue2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
from: {
|
||||||
|
emailAddress: {
|
||||||
|
address: 'me@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
importance: 'Normal',
|
||||||
|
isReadReceiptRequested: true,
|
||||||
|
replyTo: [
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
address: 'test4@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
subject: 'Test Subject',
|
||||||
|
toRecipients: [
|
||||||
|
{
|
||||||
|
emailAddress: {
|
||||||
|
address: 'to@mail.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const message = createMessage(fields);
|
||||||
|
|
||||||
|
expect(message).toEqual(result);
|
||||||
|
});
|
||||||
|
});
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,29 @@
|
||||||
|
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||||
|
|
||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeBaseDescription,
|
||||||
|
INodeTypeDescription,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { loadOptions, listSearch } from './methods';
|
||||||
|
import { description } from './actions/node.description';
|
||||||
|
import { router } from './actions/router';
|
||||||
|
|
||||||
|
export class MicrosoftOutlookV2 implements INodeType {
|
||||||
|
description: INodeTypeDescription;
|
||||||
|
|
||||||
|
constructor(baseDescription: INodeTypeBaseDescription) {
|
||||||
|
this.description = {
|
||||||
|
...baseDescription,
|
||||||
|
...description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
methods = { loadOptions, listSearch };
|
||||||
|
|
||||||
|
async execute(this: IExecuteFunctions) {
|
||||||
|
return router.call(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,116 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
description: 'The name of the calendar to create',
|
||||||
|
placeholder: 'e.g. My Calendar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-options
|
||||||
|
displayName: 'Calendar Group',
|
||||||
|
name: 'calendarGroup',
|
||||||
|
type: 'options',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCalendarGroups',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
description:
|
||||||
|
'If set, the calendar will be created in the specified calendar group. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Color',
|
||||||
|
name: 'color',
|
||||||
|
type: 'options',
|
||||||
|
default: 'lightBlue',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Light Blue',
|
||||||
|
value: 'lightBlue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Brown',
|
||||||
|
value: 'lightBrown',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Gray',
|
||||||
|
value: 'lightGray',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Green',
|
||||||
|
value: 'lightGreen',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Orange',
|
||||||
|
value: 'lightOrange',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Pink',
|
||||||
|
value: 'lightPink',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Red',
|
||||||
|
value: 'lightRed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Teal',
|
||||||
|
value: 'lightTeal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Yellow',
|
||||||
|
value: 'lightYellow',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
description: 'Specify the color to distinguish the calendar from the others',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['calendar'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', index);
|
||||||
|
const name = this.getNodeParameter('name', index) as string;
|
||||||
|
|
||||||
|
let endpoint = '/calendars';
|
||||||
|
|
||||||
|
if (additionalFields.calendarGroup) {
|
||||||
|
endpoint = `/calendarGroups/${additionalFields.calendarGroup}/calendars`;
|
||||||
|
delete additionalFields.calendarGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
name,
|
||||||
|
...additionalFields,
|
||||||
|
};
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(this, 'POST', endpoint, body);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
import type { IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { calendarRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [calendarRLC];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['calendar'],
|
||||||
|
operation: ['delete'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const calendarId = this.getNodeParameter('calendarId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
await microsoftApiRequest.call(this, 'DELETE', `/calendars/${calendarId}`);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ success: true }),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { calendarRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [calendarRLC];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['calendar'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
const calendarId = this.getNodeParameter('calendarId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/calendars/${calendarId}`,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest, microsoftApiRequestAllItems } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { returnAllOrLimit } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
...returnAllOrLimit,
|
||||||
|
{
|
||||||
|
displayName: 'Filters',
|
||||||
|
name: 'filters',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Filter',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Filter Query',
|
||||||
|
name: 'custom',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g. canShare eq true',
|
||||||
|
hint: 'Search query to filter calendars. <a href="https://learn.microsoft.com/en-us/graph/filter-query-parameter">More info</a>.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['calendar'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
let responseData;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', index);
|
||||||
|
const filters = this.getNodeParameter('filters', index, {});
|
||||||
|
|
||||||
|
if (Object.keys(filters).length) {
|
||||||
|
const filterString: string[] = [];
|
||||||
|
|
||||||
|
if (filters.custom) {
|
||||||
|
filterString.push(filters.custom as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterString.length) {
|
||||||
|
qs.$filter = filterString.join(' and ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = '/calendars';
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qs.$top = this.getNodeParameter('limit', index);
|
||||||
|
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||||
|
responseData = responseData.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import * as create from './create.operation';
|
||||||
|
import * as get from './get.operation';
|
||||||
|
import * as getAll from './getAll.operation';
|
||||||
|
import * as del from './delete.operation';
|
||||||
|
import * as update from './update.operation';
|
||||||
|
|
||||||
|
export { create, del as delete, get, getAll, update };
|
||||||
|
|
||||||
|
export const description: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['calendar'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new calendar',
|
||||||
|
action: 'Create a calendar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a calendar',
|
||||||
|
action: 'Delete a calendar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Retrieve a calendar',
|
||||||
|
action: 'Get a calendar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'List and search calendars',
|
||||||
|
action: 'Get many calendars',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update a calendar',
|
||||||
|
action: 'Update a calendar',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'getAll',
|
||||||
|
},
|
||||||
|
|
||||||
|
...create.description,
|
||||||
|
...del.description,
|
||||||
|
...get.description,
|
||||||
|
...getAll.description,
|
||||||
|
...update.description,
|
||||||
|
];
|
|
@ -0,0 +1,108 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { calendarRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
calendarRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Update Fields',
|
||||||
|
name: 'updateFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Color',
|
||||||
|
name: 'color',
|
||||||
|
type: 'options',
|
||||||
|
default: 'lightBlue',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Light Blue',
|
||||||
|
value: 'lightBlue',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Brown',
|
||||||
|
value: 'lightBrown',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Gray',
|
||||||
|
value: 'lightGray',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Green',
|
||||||
|
value: 'lightGreen',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Orange',
|
||||||
|
value: 'lightOrange',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Pink',
|
||||||
|
value: 'lightPink',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Red',
|
||||||
|
value: 'lightRed',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Teal',
|
||||||
|
value: 'lightTeal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Light Yellow',
|
||||||
|
value: 'lightYellow',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
description: 'Specify the color to distinguish the calendar from the others',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Default Calendar',
|
||||||
|
name: 'isDefaultCalendar',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g. My Calendar',
|
||||||
|
description: 'The name of the calendar',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['calendar'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const updateFields = this.getNodeParameter('updateFields', index);
|
||||||
|
|
||||||
|
const calendarId = this.getNodeParameter('calendarId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const endpoint = `/calendars/${calendarId}`;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
...updateFields,
|
||||||
|
};
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(this, 'PATCH', endpoint, body);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { prepareContactFields } from '../../helpers/utils';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { contactFields } from '../../descriptions';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'First Name',
|
||||||
|
name: 'givenName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Last Name',
|
||||||
|
name: 'surname',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: contactFields,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['contact'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', index);
|
||||||
|
const givenName = this.getNodeParameter('givenName', index) as string;
|
||||||
|
const surname = this.getNodeParameter('surname', index) as string;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
givenName,
|
||||||
|
...prepareContactFields(additionalFields),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (surname) {
|
||||||
|
body.surname = surname;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(this, 'POST', '/contacts', body);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import type { IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { contactRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [contactRLC];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['contact'],
|
||||||
|
operation: ['delete'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const contactId = this.getNodeParameter('contactId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
await microsoftApiRequest.call(this, 'DELETE', `/contacts/${contactId}`);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ success: true }),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { contactFields } from '../../helpers/utils';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { contactRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
contactRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Output',
|
||||||
|
name: 'output',
|
||||||
|
type: 'options',
|
||||||
|
default: 'simple',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Simplified',
|
||||||
|
value: 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Raw',
|
||||||
|
value: 'raw',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Select Included Fields',
|
||||||
|
value: 'fields',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description: 'The fields to add to the output',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
output: ['fields'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: contactFields,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['contact'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
const contactId = this.getNodeParameter('contactId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const output = this.getNodeParameter('output', index) as string;
|
||||||
|
|
||||||
|
if (output === 'fields') {
|
||||||
|
const fields = this.getNodeParameter('fields', index) as string[];
|
||||||
|
qs.$select = fields.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output === 'simple') {
|
||||||
|
qs.$select = 'id,displayName,emailAddresses,businessPhones,mobilePhone';
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/contacts/${contactId}`,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { contactFields } from '../../helpers/utils';
|
||||||
|
import { microsoftApiRequest, microsoftApiRequestAllItems } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { returnAllOrLimit } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
...returnAllOrLimit,
|
||||||
|
{
|
||||||
|
displayName: 'Output',
|
||||||
|
name: 'output',
|
||||||
|
type: 'options',
|
||||||
|
default: 'simple',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Simplified',
|
||||||
|
value: 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Raw',
|
||||||
|
value: 'raw',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Select Included Fields',
|
||||||
|
value: 'fields',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description: 'The fields to add to the output',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
output: ['fields'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: contactFields,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filters',
|
||||||
|
name: 'filters',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Filter',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Filter Query',
|
||||||
|
name: 'custom',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: "e.g. displayName eq 'John Doe'",
|
||||||
|
hint: 'Search query to filter contacts. <a href="https://learn.microsoft.com/en-us/graph/filter-query-parameter">More info</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Email Address',
|
||||||
|
name: 'emailAddress',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'If contacts that you want to retrieve have multiple email addresses, you can enter them separated by commas',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['contact'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
let responseData;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', index);
|
||||||
|
const filters = this.getNodeParameter('filters', index, {});
|
||||||
|
const output = this.getNodeParameter('output', index) as string;
|
||||||
|
|
||||||
|
if (output === 'fields') {
|
||||||
|
const fields = this.getNodeParameter('fields', index) as string[];
|
||||||
|
qs.$select = fields.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output === 'simple') {
|
||||||
|
qs.$select = 'id,displayName,emailAddresses,businessPhones,mobilePhone';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(filters).length) {
|
||||||
|
const filterString: string[] = [];
|
||||||
|
|
||||||
|
if (filters.emailAddress) {
|
||||||
|
const emails = (filters.emailAddress as string)
|
||||||
|
.split(',')
|
||||||
|
.map((email) => `emailAddresses/any(a:a/address eq '${email.trim()}')`);
|
||||||
|
filterString.push(emails.join(' and '));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.custom) {
|
||||||
|
filterString.push(filters.custom as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterString.length) {
|
||||||
|
qs.$filter = filterString.join(' and ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = '/contacts';
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qs.$top = this.getNodeParameter('limit', index);
|
||||||
|
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||||
|
responseData = responseData.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import * as create from './create.operation';
|
||||||
|
import * as del from './delete.operation';
|
||||||
|
import * as getAll from './getAll.operation';
|
||||||
|
import * as get from './get.operation';
|
||||||
|
import * as update from './update.operation';
|
||||||
|
|
||||||
|
export { create, del as delete, get, getAll, update };
|
||||||
|
|
||||||
|
export const description: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['contact'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new contact',
|
||||||
|
action: 'Create a contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a contact',
|
||||||
|
action: 'Delete a contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Retrieve a contact',
|
||||||
|
action: 'Get a contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'List and search contacts',
|
||||||
|
action: 'Get many contacts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update a contact',
|
||||||
|
action: 'Update a contact',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'getAll',
|
||||||
|
},
|
||||||
|
...create.description,
|
||||||
|
...del.description,
|
||||||
|
...get.description,
|
||||||
|
...getAll.description,
|
||||||
|
...update.description,
|
||||||
|
];
|
|
@ -0,0 +1,49 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { prepareContactFields } from '../../helpers/utils';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { contactFields, contactRLC } from '../../descriptions';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
contactRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Update Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: contactFields,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['contact'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', index);
|
||||||
|
const contactId = this.getNodeParameter('contactId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const body: IDataObject = prepareContactFields(additionalFields);
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PATCH',
|
||||||
|
`/contacts/${contactId}`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,259 @@
|
||||||
|
import type {
|
||||||
|
IBinaryKeyData,
|
||||||
|
IDataObject,
|
||||||
|
IExecuteFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
import { createMessage } from '../../helpers/utils';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Subject',
|
||||||
|
name: 'subject',
|
||||||
|
description: 'The subject of the message',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message',
|
||||||
|
name: 'bodyContent',
|
||||||
|
description: 'Message body content',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
rows: 2,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Attachments',
|
||||||
|
name: 'attachments',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Attachment',
|
||||||
|
default: {},
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'attachments',
|
||||||
|
displayName: 'Attachment',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Input Data Field Name',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g. data',
|
||||||
|
hint: 'The name of the input field containing the binary file data to be attached',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'BCC Recipients',
|
||||||
|
name: 'bccRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of BCC recipients',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. john@example.com',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Category Names or IDs',
|
||||||
|
name: 'categories',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description:
|
||||||
|
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCategoriesNames',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CC Recipients',
|
||||||
|
name: 'ccRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of CC recipients',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. john@example.com',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Headers',
|
||||||
|
name: 'internetMessageHeaders',
|
||||||
|
placeholder: 'Add Header',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'headers',
|
||||||
|
displayName: 'Header',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the header',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value to set for the header',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'From',
|
||||||
|
name: 'from',
|
||||||
|
description:
|
||||||
|
'The owner of the mailbox from which the message is sent. Must correspond to the actual mailbox used.',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. john@example.com',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Importance',
|
||||||
|
name: 'importance',
|
||||||
|
description: 'The importance of the message',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Low',
|
||||||
|
value: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'High',
|
||||||
|
value: 'High',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message Type',
|
||||||
|
name: 'bodyContentType',
|
||||||
|
description: 'Message body content type',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'HTML',
|
||||||
|
value: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Text',
|
||||||
|
value: 'Text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Read Receipt Requested',
|
||||||
|
name: 'isReadReceiptRequested',
|
||||||
|
description: 'Whether a read receipt is requested for the message',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Reply To',
|
||||||
|
name: 'replyTo',
|
||||||
|
description: 'Email address to use when replying',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. replyto@example.com',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'To',
|
||||||
|
name: 'toRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of recipients',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. john@example.com',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['draft'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number, items: INodeExecutionData[]) {
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', index);
|
||||||
|
const subject = this.getNodeParameter('subject', index) as string;
|
||||||
|
const bodyContent = this.getNodeParameter('bodyContent', index, '') as string;
|
||||||
|
|
||||||
|
additionalFields.subject = subject;
|
||||||
|
|
||||||
|
additionalFields.bodyContent = bodyContent || ' ';
|
||||||
|
|
||||||
|
// Create message object from optional fields
|
||||||
|
const body: IDataObject = createMessage(additionalFields);
|
||||||
|
|
||||||
|
if (additionalFields.attachments) {
|
||||||
|
const attachments = (additionalFields.attachments as IDataObject).attachments as IDataObject[];
|
||||||
|
|
||||||
|
// // Handle attachments
|
||||||
|
body.attachments = attachments.map((attachment) => {
|
||||||
|
const binaryPropertyName = attachment.binaryPropertyName as string;
|
||||||
|
|
||||||
|
if (items[index].binary === undefined) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'No binary data exists on item!', {
|
||||||
|
itemIndex: index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
items[index].binary &&
|
||||||
|
(items[index].binary as IDataObject)[binaryPropertyName] === undefined
|
||||||
|
) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`No binary data property "${binaryPropertyName}" does not exists on item!`,
|
||||||
|
{ itemIndex: index },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryData = (items[index].binary as IBinaryKeyData)[binaryPropertyName];
|
||||||
|
return {
|
||||||
|
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||||
|
name: binaryData.fileName,
|
||||||
|
contentBytes: binaryData.data,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(this, 'POST', '/messages', body, {});
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import type { IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { draftRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [draftRLC];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['draft'],
|
||||||
|
operation: ['delete'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const draftId = this.getNodeParameter('draftId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
await microsoftApiRequest.call(this, 'DELETE', `/messages/${draftId}`);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ success: true }),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,127 @@
|
||||||
|
import type {
|
||||||
|
IDataObject,
|
||||||
|
IExecuteFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { messageFields, simplifyOutputMessages } from '../../helpers/utils';
|
||||||
|
import { downloadAttachments, microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { draftRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
draftRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Output',
|
||||||
|
name: 'output',
|
||||||
|
type: 'options',
|
||||||
|
default: 'simple',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Simplified',
|
||||||
|
value: 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Raw',
|
||||||
|
value: 'raw',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Select Included Fields',
|
||||||
|
value: 'fields',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description: 'The fields to add to the output',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
output: ['fields'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: messageFields,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Attachments Prefix',
|
||||||
|
name: 'attachmentsPrefix',
|
||||||
|
type: 'string',
|
||||||
|
default: 'attachment_',
|
||||||
|
description:
|
||||||
|
'Prefix for name of the output fields to put the binary files data in. An index starting from 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0".',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Download Attachments',
|
||||||
|
name: 'downloadAttachments',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description:
|
||||||
|
"Whether the message's attachments will be downloaded and included in the output",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['draft'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
let responseData;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
const draftId = this.getNodeParameter('draftId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
const options = this.getNodeParameter('options', index, {});
|
||||||
|
const output = this.getNodeParameter('output', index) as string;
|
||||||
|
|
||||||
|
if (output === 'fields') {
|
||||||
|
const fields = this.getNodeParameter('fields', index) as string[];
|
||||||
|
|
||||||
|
if (options.downloadAttachments) {
|
||||||
|
fields.push('hasAttachments');
|
||||||
|
}
|
||||||
|
|
||||||
|
qs.$select = fields.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output === 'simple') {
|
||||||
|
qs.$select =
|
||||||
|
'id,conversationId,subject,bodyPreview,from,toRecipients,categories,hasAttachments';
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(this, 'GET', `/messages/${draftId}`, undefined, qs);
|
||||||
|
|
||||||
|
if (output === 'simple') {
|
||||||
|
responseData = simplifyOutputMessages([responseData as IDataObject]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let executionData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
|
if (options.downloadAttachments) {
|
||||||
|
const prefix = (options.attachmentsPrefix as string) || 'attachment_';
|
||||||
|
executionData = await downloadAttachments.call(this, responseData as IDataObject, prefix);
|
||||||
|
} else {
|
||||||
|
executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import * as create from './create.operation';
|
||||||
|
import * as del from './delete.operation';
|
||||||
|
import * as get from './get.operation';
|
||||||
|
import * as send from './send.operation';
|
||||||
|
import * as update from './update.operation';
|
||||||
|
|
||||||
|
export { create, del as delete, get, send, update };
|
||||||
|
|
||||||
|
export const description: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['draft'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new email draft',
|
||||||
|
action: 'Create a draft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete an email draft',
|
||||||
|
action: 'Delete a draft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Retrieve an email draft',
|
||||||
|
action: 'Get a draft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Send',
|
||||||
|
value: 'send',
|
||||||
|
description: 'Send an existing email draft',
|
||||||
|
action: 'Send a draft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update an email draft',
|
||||||
|
action: 'Update a draft',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
},
|
||||||
|
|
||||||
|
...create.description,
|
||||||
|
...del.description,
|
||||||
|
...get.description,
|
||||||
|
...send.description,
|
||||||
|
...update.description,
|
||||||
|
];
|
|
@ -0,0 +1,52 @@
|
||||||
|
import type { IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { makeRecipient } from '../../helpers/utils';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { draftRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
draftRLC,
|
||||||
|
{
|
||||||
|
displayName: 'To',
|
||||||
|
name: 'to',
|
||||||
|
description: 'Comma-separated list of email addresses of recipients',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['draft'],
|
||||||
|
operation: ['send'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const draftId = this.getNodeParameter('draftId', index, undefined, { extractValue: true });
|
||||||
|
const to = this.getNodeParameter('to', index) as string;
|
||||||
|
|
||||||
|
if (to) {
|
||||||
|
const recipients = to
|
||||||
|
.split(',')
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter((email) => email);
|
||||||
|
|
||||||
|
if (recipients.length !== 0) {
|
||||||
|
await microsoftApiRequest.call(this, 'PATCH', `/messages/${draftId}`, {
|
||||||
|
toRecipients: recipients.map((recipient: string) => makeRecipient(recipient)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await microsoftApiRequest.call(this, 'POST', `/messages/${draftId}/send`);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ success: true }),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,206 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { createMessage } from '../../helpers/utils';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { draftRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
draftRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Update Fields',
|
||||||
|
name: 'updateFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'BCC Recipients',
|
||||||
|
name: 'bccRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of BCC recipients',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. john@example.com',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Category Names or IDs',
|
||||||
|
name: 'categories',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description:
|
||||||
|
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCategoriesNames',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CC Recipients',
|
||||||
|
name: 'ccRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of CC recipients',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. john@example.com',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Headers',
|
||||||
|
name: 'internetMessageHeaders',
|
||||||
|
placeholder: 'Add Header',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'headers',
|
||||||
|
displayName: 'Header',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the header',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value to set for the header',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'From',
|
||||||
|
name: 'from',
|
||||||
|
description:
|
||||||
|
'The owner of the mailbox from which the message is sent. Must correspond to the actual mailbox used.',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. john@example.com',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Importance',
|
||||||
|
name: 'importance',
|
||||||
|
description: 'The importance of the message',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Low',
|
||||||
|
value: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'High',
|
||||||
|
value: 'High',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Is Read',
|
||||||
|
name: 'isRead',
|
||||||
|
description: 'Whether the message must be marked as read',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message',
|
||||||
|
name: 'bodyContent',
|
||||||
|
description: 'Message body content',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
rows: 2,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message Type',
|
||||||
|
name: 'bodyContentType',
|
||||||
|
description: 'Message body content type',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'HTML',
|
||||||
|
value: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Text',
|
||||||
|
value: 'Text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Read Receipt Requested',
|
||||||
|
name: 'isReadReceiptRequested',
|
||||||
|
description: 'Whether a read receipt is requested for the message',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Reply To',
|
||||||
|
name: 'replyTo',
|
||||||
|
description: 'Email address to use when replying',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. replyto@example.com',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Subject',
|
||||||
|
name: 'subject',
|
||||||
|
description: 'The subject of the message',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'To',
|
||||||
|
name: 'toRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of recipients',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. john@example.com',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['draft'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const draftId = this.getNodeParameter('draftId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const updateFields = this.getNodeParameter('updateFields', index);
|
||||||
|
|
||||||
|
// Create message from optional fields
|
||||||
|
const body: IDataObject = createMessage(updateFields);
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'PATCH',
|
||||||
|
`/messages/${draftId}`,
|
||||||
|
body,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,291 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { calendarRLC } from '../../descriptions';
|
||||||
|
import moment from 'moment-timezone';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
calendarRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Title',
|
||||||
|
name: 'subject',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Start',
|
||||||
|
name: 'startDateTime',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: DateTime.now().toISO(),
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'End',
|
||||||
|
name: 'endDateTime',
|
||||||
|
type: 'dateTime',
|
||||||
|
required: true,
|
||||||
|
default: DateTime.now().plus({ minutes: 30 }).toISO(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options
|
||||||
|
displayName: 'Categories',
|
||||||
|
name: 'categories',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description:
|
||||||
|
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCategoriesNames',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Description',
|
||||||
|
name: 'body',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
rows: 2,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Description Preview',
|
||||||
|
name: 'bodyPreview',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Hide Attendees',
|
||||||
|
name: 'hideAttendees',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description:
|
||||||
|
'Whether to allow each attendee to only see themselves in the meeting request and meeting tracking list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Importance',
|
||||||
|
name: 'importance',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Low',
|
||||||
|
value: 'low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'High',
|
||||||
|
value: 'high',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Is All Day',
|
||||||
|
name: 'isAllDay',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Is Cancelled',
|
||||||
|
name: 'isCancelled',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Is Draft',
|
||||||
|
name: 'isDraft',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Is Online Meeting',
|
||||||
|
name: 'isOnlineMeeting',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sensitivity',
|
||||||
|
name: 'sensitivity',
|
||||||
|
type: 'options',
|
||||||
|
default: 'normal',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Personal',
|
||||||
|
value: 'personal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Private',
|
||||||
|
value: 'private',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Confidential',
|
||||||
|
value: 'confidential',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Show As',
|
||||||
|
name: 'showAs',
|
||||||
|
type: 'options',
|
||||||
|
default: 'free',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Busy',
|
||||||
|
value: 'busy',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Free',
|
||||||
|
value: 'free',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Oof',
|
||||||
|
value: 'oof',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Tentative',
|
||||||
|
value: 'tentative',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Working Elsewhere',
|
||||||
|
value: 'workingElsewhere',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Timezone',
|
||||||
|
name: 'timeZone',
|
||||||
|
type: 'options',
|
||||||
|
default: 'UTC',
|
||||||
|
options: moment.tz.names().map((name) => ({
|
||||||
|
name,
|
||||||
|
value: name,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Type',
|
||||||
|
name: 'type',
|
||||||
|
type: 'options',
|
||||||
|
default: 'singleInstance',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Single Instance',
|
||||||
|
value: 'singleInstance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Occurrence',
|
||||||
|
value: 'occurrence',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Exception',
|
||||||
|
value: 'exception',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Series Master',
|
||||||
|
value: 'seriesMaster',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['event'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
let additionalFields = this.getNodeParameter('additionalFields', index);
|
||||||
|
|
||||||
|
additionalFields = Object.keys(additionalFields).reduce((acc: IDataObject, key: string) => {
|
||||||
|
if (additionalFields[key] !== '' || additionalFields[key] !== undefined) {
|
||||||
|
acc[key] = additionalFields[key];
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const calendarId = this.getNodeParameter('calendarId', index, '', {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
if (calendarId === '') {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'Calendar ID is required');
|
||||||
|
}
|
||||||
|
const subject = this.getNodeParameter('subject', index) as string;
|
||||||
|
|
||||||
|
const endpoint = `/calendars/${calendarId}/events`;
|
||||||
|
|
||||||
|
let timeZone = 'UTC';
|
||||||
|
|
||||||
|
if (additionalFields.timeZone) {
|
||||||
|
timeZone = additionalFields.timeZone as string;
|
||||||
|
delete additionalFields.timeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.body) {
|
||||||
|
additionalFields.body = {
|
||||||
|
content: additionalFields.body,
|
||||||
|
contentType: 'html',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let startDateTime = this.getNodeParameter('startDateTime', index) as string;
|
||||||
|
let endDateTime = this.getNodeParameter('endDateTime', index) as string;
|
||||||
|
|
||||||
|
if (additionalFields.isAllDay) {
|
||||||
|
startDateTime = DateTime.fromISO(startDateTime, { zone: timeZone }).toFormat('yyyy-MM-dd');
|
||||||
|
endDateTime = DateTime.fromISO(endDateTime, { zone: timeZone }).toFormat('yyyy-MM-dd');
|
||||||
|
|
||||||
|
const minimalWholeDayDuration = 24;
|
||||||
|
const duration = DateTime.fromISO(startDateTime, { zone: timeZone }).diff(
|
||||||
|
DateTime.fromISO(endDateTime, { zone: timeZone }),
|
||||||
|
).hours;
|
||||||
|
|
||||||
|
if (duration < minimalWholeDayDuration) {
|
||||||
|
endDateTime = DateTime.fromISO(startDateTime, { zone: timeZone }).plus({ hours: 24 }).toISO();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
subject,
|
||||||
|
start: {
|
||||||
|
dateTime: startDateTime,
|
||||||
|
timeZone,
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
dateTime: endDateTime,
|
||||||
|
timeZone,
|
||||||
|
},
|
||||||
|
...additionalFields,
|
||||||
|
};
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(this, 'POST', endpoint, body);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import type { IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { calendarRLC, eventRLC } from '../../descriptions';
|
||||||
|
import { decodeOutlookId } from '../../helpers/utils';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [calendarRLC, eventRLC];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['event'],
|
||||||
|
operation: ['delete'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const eventId = decodeOutlookId(
|
||||||
|
this.getNodeParameter('eventId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string,
|
||||||
|
);
|
||||||
|
|
||||||
|
await microsoftApiRequest.call(this, 'DELETE', `/calendar/events/${eventId}`);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ success: true }),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { decodeOutlookId, eventfields } from '../../helpers/utils';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { calendarRLC, eventRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
calendarRLC,
|
||||||
|
eventRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Output',
|
||||||
|
name: 'output',
|
||||||
|
type: 'options',
|
||||||
|
default: 'simple',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Simplified',
|
||||||
|
value: 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Raw',
|
||||||
|
value: 'raw',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Select Included Fields',
|
||||||
|
value: 'fields',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description: 'The fields to add to the output',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
output: ['fields'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: eventfields,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['event'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
|
||||||
|
const eventId = decodeOutlookId(
|
||||||
|
this.getNodeParameter('eventId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string,
|
||||||
|
);
|
||||||
|
|
||||||
|
const output = this.getNodeParameter('output', index) as string;
|
||||||
|
|
||||||
|
if (output === 'fields') {
|
||||||
|
const fields = this.getNodeParameter('fields', index) as string[];
|
||||||
|
qs.$select = fields.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output === 'simple') {
|
||||||
|
qs.$select = 'id,subject,bodyPreview,start,end,organizer,attendees,webLink';
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `/calendar/events/${eventId}`;
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { eventfields } from '../../helpers/utils';
|
||||||
|
import { microsoftApiRequest, microsoftApiRequestAllItems } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { calendarRLC, returnAllOrLimit } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'From All Calendars',
|
||||||
|
name: 'fromAllCalendars',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...calendarRLC,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
fromAllCalendars: [false],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...returnAllOrLimit,
|
||||||
|
{
|
||||||
|
displayName: 'Output',
|
||||||
|
name: 'output',
|
||||||
|
type: 'options',
|
||||||
|
default: 'simple',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Simplified',
|
||||||
|
value: 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Raw',
|
||||||
|
value: 'raw',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Select Included Fields',
|
||||||
|
value: 'fields',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description: 'The fields to add to the output',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
output: ['fields'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: eventfields,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filters',
|
||||||
|
name: 'filters',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Filter',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Filter Query',
|
||||||
|
name: 'custom',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: "e.g. contains(subject,'Hello')",
|
||||||
|
hint: 'Search query to filter events. <a href="https://learn.microsoft.com/en-us/graph/filter-query-parameter">More info</a>.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['event'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const responseData: IDataObject[] = [];
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', index);
|
||||||
|
const filters = this.getNodeParameter('filters', index, {});
|
||||||
|
const output = this.getNodeParameter('output', index) as string;
|
||||||
|
|
||||||
|
if (output === 'fields') {
|
||||||
|
const fields = this.getNodeParameter('fields', index) as string[];
|
||||||
|
qs.$select = fields.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output === 'simple') {
|
||||||
|
qs.$select = 'id,subject,bodyPreview,start,end,organizer,attendees,webLink';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(filters).length) {
|
||||||
|
const filterString: string[] = [];
|
||||||
|
|
||||||
|
if (filters.custom) {
|
||||||
|
filterString.push(filters.custom as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterString.length) {
|
||||||
|
qs.$filter = filterString.join(' and ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const calendars: string[] = [];
|
||||||
|
|
||||||
|
const fromAllCalendars = this.getNodeParameter('fromAllCalendars', index) as boolean;
|
||||||
|
|
||||||
|
if (fromAllCalendars) {
|
||||||
|
const response = await microsoftApiRequest.call(this, 'GET', '/calendars', undefined, {
|
||||||
|
$select: 'id',
|
||||||
|
});
|
||||||
|
for (const calendar of response.value) {
|
||||||
|
calendars.push(calendar.id as string);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const calendarId = this.getNodeParameter('calendarId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
calendars.push(calendarId);
|
||||||
|
}
|
||||||
|
const limit = this.getNodeParameter('limit', index, 0);
|
||||||
|
|
||||||
|
for (const calendarId of calendars) {
|
||||||
|
const endpoint = `/calendars/${calendarId}/events`;
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
const response = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
responseData.push(...response);
|
||||||
|
} else {
|
||||||
|
qs.$top = limit - responseData.length;
|
||||||
|
|
||||||
|
if (qs.$top <= 0) break;
|
||||||
|
|
||||||
|
const response = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||||
|
responseData.push(...response.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import * as create from './create.operation';
|
||||||
|
import * as get from './get.operation';
|
||||||
|
import * as getAll from './getAll.operation';
|
||||||
|
import * as del from './delete.operation';
|
||||||
|
import * as update from './update.operation';
|
||||||
|
|
||||||
|
export { create, del as delete, get, getAll, update };
|
||||||
|
|
||||||
|
export const description: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['event'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a new event',
|
||||||
|
action: 'Create an event',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete an event',
|
||||||
|
action: 'Delete an event',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Retrieve an event',
|
||||||
|
action: 'Get an event',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'List and search events',
|
||||||
|
action: 'Get many events',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update an event',
|
||||||
|
action: 'Update an event',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'getAll',
|
||||||
|
},
|
||||||
|
|
||||||
|
...create.description,
|
||||||
|
...del.description,
|
||||||
|
...get.description,
|
||||||
|
...getAll.description,
|
||||||
|
...update.description,
|
||||||
|
];
|
|
@ -0,0 +1,283 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import { calendarRLC, eventRLC } from '../../descriptions';
|
||||||
|
import { decodeOutlookId } from '../../helpers/utils';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
calendarRLC,
|
||||||
|
eventRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-wrong-for-dynamic-multi-options
|
||||||
|
displayName: 'Categories',
|
||||||
|
name: 'categories',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description:
|
||||||
|
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCategoriesNames',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Description',
|
||||||
|
name: 'body',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
rows: 2,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Description Preview',
|
||||||
|
name: 'bodyPreview',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'End',
|
||||||
|
name: 'end',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Hide Attendees',
|
||||||
|
name: 'hideAttendees',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description:
|
||||||
|
'Whether to allow each attendee to only see themselves in the meeting request and meeting tracking list',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Importance',
|
||||||
|
name: 'importance',
|
||||||
|
type: 'options',
|
||||||
|
default: 'low',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Low',
|
||||||
|
value: 'low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'High',
|
||||||
|
value: 'high',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Is All Day',
|
||||||
|
name: 'isAllDay',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Is Cancelled',
|
||||||
|
name: 'isCancelled',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Is Draft',
|
||||||
|
name: 'isDraft',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Is Online Meeting',
|
||||||
|
name: 'isOnlineMeeting',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sensitivity',
|
||||||
|
name: 'sensitivity',
|
||||||
|
type: 'options',
|
||||||
|
default: 'normal',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Personal',
|
||||||
|
value: 'personal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Private',
|
||||||
|
value: 'private',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Confidential',
|
||||||
|
value: 'confidential',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Show As',
|
||||||
|
name: 'showAs',
|
||||||
|
type: 'options',
|
||||||
|
default: 'free',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Busy',
|
||||||
|
value: 'busy',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Free',
|
||||||
|
value: 'free',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Oof',
|
||||||
|
value: 'oof',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Tentative',
|
||||||
|
value: 'tentative',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Working Elsewhere',
|
||||||
|
value: 'workingElsewhere',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Start',
|
||||||
|
name: 'start',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Timezone',
|
||||||
|
name: 'timeZone',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Title',
|
||||||
|
name: 'subject',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Type',
|
||||||
|
name: 'type',
|
||||||
|
type: 'options',
|
||||||
|
default: 'singleInstance',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Single Instance',
|
||||||
|
value: 'singleInstance',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Occurrence',
|
||||||
|
value: 'occurrence',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Exception',
|
||||||
|
value: 'exception',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Series Master',
|
||||||
|
value: 'seriesMaster',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['event'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', index);
|
||||||
|
|
||||||
|
const eventId = decodeOutlookId(
|
||||||
|
this.getNodeParameter('eventId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string,
|
||||||
|
);
|
||||||
|
|
||||||
|
let timeZone = 'UTC';
|
||||||
|
|
||||||
|
if (additionalFields.timeZone) {
|
||||||
|
timeZone = additionalFields.timeZone as string;
|
||||||
|
delete additionalFields.timeZone;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (additionalFields.body) {
|
||||||
|
additionalFields.body = {
|
||||||
|
content: additionalFields.body,
|
||||||
|
contentType: 'html',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let startDateTime = additionalFields.start as string;
|
||||||
|
let endDateTime = additionalFields.end as string;
|
||||||
|
|
||||||
|
if (additionalFields.isAllDay) {
|
||||||
|
startDateTime =
|
||||||
|
DateTime.fromISO(startDateTime, { zone: timeZone }).toFormat('yyyy-MM-dd') ||
|
||||||
|
DateTime.utc().toFormat('yyyy-MM-dd');
|
||||||
|
endDateTime =
|
||||||
|
DateTime.fromISO(endDateTime, { zone: timeZone }).toFormat('yyyy-MM-dd') ||
|
||||||
|
DateTime.utc().toFormat('yyyy-MM-dd');
|
||||||
|
|
||||||
|
const minimalWholeDayDuration = 24;
|
||||||
|
const duration = DateTime.fromISO(startDateTime, { zone: timeZone }).diff(
|
||||||
|
DateTime.fromISO(endDateTime, { zone: timeZone }),
|
||||||
|
).hours;
|
||||||
|
|
||||||
|
if (duration < minimalWholeDayDuration) {
|
||||||
|
endDateTime = DateTime.fromISO(startDateTime, { zone: timeZone }).plus({ hours: 24 }).toISO();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
...additionalFields,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (startDateTime) {
|
||||||
|
body.start = {
|
||||||
|
dateTime: startDateTime,
|
||||||
|
timeZone,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endDateTime) {
|
||||||
|
body.end = {
|
||||||
|
dateTime: endDateTime,
|
||||||
|
timeZone,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `/calendar/events/${eventId}`;
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(this, 'PATCH', endpoint, body);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { folderRLC } from '../../descriptions';
|
||||||
|
import { decodeOutlookId } from '../../helpers/utils';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'displayName',
|
||||||
|
description: 'Name of the folder',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g. My Folder',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
options: [{ ...folderRLC, displayName: 'Parent Folder', required: false }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['folder'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const displayName = this.getNodeParameter('displayName', index) as string;
|
||||||
|
|
||||||
|
const folderId = decodeOutlookId(
|
||||||
|
this.getNodeParameter('options.folderId', index, '', {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string,
|
||||||
|
);
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
displayName,
|
||||||
|
};
|
||||||
|
|
||||||
|
let endpoint;
|
||||||
|
|
||||||
|
if (folderId) {
|
||||||
|
endpoint = `/mailFolders/${folderId}/childFolders`;
|
||||||
|
} else {
|
||||||
|
endpoint = '/mailFolders';
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(this, 'POST', endpoint, body);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
import type { IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { folderRLC } from '../../descriptions';
|
||||||
|
import { decodeOutlookId } from '../../helpers/utils';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [folderRLC];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['folder'],
|
||||||
|
operation: ['delete'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const folderId = decodeOutlookId(
|
||||||
|
this.getNodeParameter('folderId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string,
|
||||||
|
);
|
||||||
|
|
||||||
|
await microsoftApiRequest.call(this, 'DELETE', `/mailFolders/${folderId}`);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ success: true }),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { folderFields, folderRLC } from '../../descriptions';
|
||||||
|
import { decodeOutlookId } from '../../helpers/utils';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
folderRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description: 'The fields to add to the output',
|
||||||
|
options: folderFields,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['folder'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
const folderId = decodeOutlookId(
|
||||||
|
this.getNodeParameter('folderId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string,
|
||||||
|
);
|
||||||
|
|
||||||
|
const options = this.getNodeParameter('options', index);
|
||||||
|
|
||||||
|
if (options.fields) {
|
||||||
|
qs.$select = (options.fields as string[]).join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.filter) {
|
||||||
|
qs.$filter = options.filter;
|
||||||
|
}
|
||||||
|
const responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/mailFolders/${folderId}`,
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,111 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { getSubfolders, microsoftApiRequest, microsoftApiRequestAllItems } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { folderFields, folderRLC, returnAllOrLimit } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
...returnAllOrLimit,
|
||||||
|
{
|
||||||
|
displayName: 'Filters',
|
||||||
|
name: 'filters',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Filter',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Filter Query',
|
||||||
|
name: 'filter',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: "e.g. displayName eq 'My Folder'",
|
||||||
|
hint: 'Search query to filter folders. <a href="https://docs.microsoft.com/en-us/graph/query-parameters#filter-parameter">More info</a>.',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description: 'The fields to add to the output',
|
||||||
|
options: folderFields,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Include Child Folders',
|
||||||
|
name: 'includeChildFolders',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to include child folders in the response',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...folderRLC,
|
||||||
|
displayName: 'Parent Folder',
|
||||||
|
required: false,
|
||||||
|
description: 'The folder you want to search in',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['folder'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
let responseData;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', index);
|
||||||
|
const options = this.getNodeParameter('options', index);
|
||||||
|
const filter = this.getNodeParameter('filters.filter', index, '') as string;
|
||||||
|
|
||||||
|
const parentFolderId = this.getNodeParameter('options.folderId', index, '', {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
if (options.fields) {
|
||||||
|
qs.$select = (options.fields as string[]).join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
qs.$filter = filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
let endpoint;
|
||||||
|
if (parentFolderId) {
|
||||||
|
endpoint = `/mailFolders/${parentFolderId}/childFolders`;
|
||||||
|
} else {
|
||||||
|
endpoint = '/mailFolders';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await microsoftApiRequestAllItems.call(this, 'value', 'GET', endpoint, {}, qs);
|
||||||
|
} else {
|
||||||
|
qs.$top = this.getNodeParameter('limit', index);
|
||||||
|
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, {}, qs);
|
||||||
|
responseData = responseData.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.includeChildFolders) {
|
||||||
|
responseData = await getSubfolders.call(this, responseData as IDataObject[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import * as create from './create.operation';
|
||||||
|
import * as del from './delete.operation';
|
||||||
|
import * as get from './get.operation';
|
||||||
|
import * as getAll from './getAll.operation';
|
||||||
|
import * as update from './update.operation';
|
||||||
|
|
||||||
|
export { create, del as delete, get, getAll, update };
|
||||||
|
|
||||||
|
export const description: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['folder'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: "Create a mail folder in the root folder of the user's mailbox",
|
||||||
|
action: 'Create a folder',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a folder',
|
||||||
|
action: 'Delete a folder',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Retrieve a folder',
|
||||||
|
action: 'Get a folder',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Get many folders',
|
||||||
|
action: 'Get many folders',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update a folder',
|
||||||
|
action: 'Update a folder',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
},
|
||||||
|
...create.description,
|
||||||
|
...del.description,
|
||||||
|
...get.description,
|
||||||
|
...getAll.description,
|
||||||
|
...update.description,
|
||||||
|
];
|
|
@ -0,0 +1,46 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { folderRLC } from '../../descriptions';
|
||||||
|
import { decodeOutlookId } from '../../helpers/utils';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
folderRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'displayName',
|
||||||
|
description: 'Name of the folder',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['folder'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const folderId = decodeOutlookId(
|
||||||
|
this.getNodeParameter('folderId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string,
|
||||||
|
);
|
||||||
|
const displayName = this.getNodeParameter('displayName', index, undefined) as string;
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(this, 'PATCH', `/mailFolders/${folderId}`, {
|
||||||
|
displayName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,302 @@
|
||||||
|
import type {
|
||||||
|
IDataObject,
|
||||||
|
IExecuteFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import {
|
||||||
|
decodeOutlookId,
|
||||||
|
messageFields,
|
||||||
|
prepareFilterString,
|
||||||
|
simplifyOutputMessages,
|
||||||
|
} from '../../helpers/utils';
|
||||||
|
import {
|
||||||
|
downloadAttachments,
|
||||||
|
microsoftApiRequest,
|
||||||
|
microsoftApiRequestAllItems,
|
||||||
|
} from '../../transport';
|
||||||
|
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { folderRLC, returnAllOrLimit } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
folderRLC,
|
||||||
|
...returnAllOrLimit,
|
||||||
|
{
|
||||||
|
displayName: 'Output',
|
||||||
|
name: 'output',
|
||||||
|
type: 'options',
|
||||||
|
default: 'simple',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Simplified',
|
||||||
|
value: 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Raw',
|
||||||
|
value: 'raw',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Select Included Fields',
|
||||||
|
value: 'fields',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description: 'The fields to add to the output',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
output: ['fields'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: messageFields,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName:
|
||||||
|
'Fetching a lot of messages may take a long time. Consider using filters to speed things up',
|
||||||
|
name: 'filtersNotice',
|
||||||
|
type: 'notice',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
returnAll: [true],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filters',
|
||||||
|
name: 'filtersUI',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Filters',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Values',
|
||||||
|
name: 'values',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Filter By',
|
||||||
|
name: 'filterBy',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Filters',
|
||||||
|
value: 'filters',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Search',
|
||||||
|
value: 'search',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'filters',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Search',
|
||||||
|
name: 'search',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g. automation',
|
||||||
|
description:
|
||||||
|
'Only return messages that contains search term. Without specific message properties, the search is carried out on the default search properties of from, subject, and body. <a href="https://docs.microsoft.com/en-us/graph/query-parameters#search-parameter target="_blank">More info</a>.',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
filterBy: ['search'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filters',
|
||||||
|
name: 'filters',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Filter',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
filterBy: ['filters'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Filter Query',
|
||||||
|
name: 'custom',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g. isRead eq false',
|
||||||
|
hint: 'Search query to filter messages. <a href="https://learn.microsoft.com/en-us/graph/filter-query-parameter">More info</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Has Attachments',
|
||||||
|
name: 'hasAttachments',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Read Status',
|
||||||
|
name: 'readStatus',
|
||||||
|
type: 'options',
|
||||||
|
default: 'unread',
|
||||||
|
hint: 'Filter messages by whether they have been read or not',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
name: 'Unread and read messages',
|
||||||
|
value: 'both',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
name: 'Unread messages only',
|
||||||
|
value: 'unread',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
name: 'Read messages only',
|
||||||
|
value: 'read',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Received After',
|
||||||
|
name: 'receivedAfter',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'Get all messages received after the specified date. In an expression you can set date using string in ISO format or a timestamp in miliseconds.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Received Before',
|
||||||
|
name: 'receivedBefore',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'Get all messages received before the specified date. In an expression you can set date using string in ISO format or a timestamp in miliseconds.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sender',
|
||||||
|
name: 'sender',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Sender name or email to filter by',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Attachments Prefix',
|
||||||
|
name: 'attachmentsPrefix',
|
||||||
|
type: 'string',
|
||||||
|
default: 'attachment_',
|
||||||
|
description:
|
||||||
|
'Prefix for name of the output fields to put the binary files data in. An index starting from 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0".',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Download Attachments',
|
||||||
|
name: 'downloadAttachments',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description:
|
||||||
|
"Whether the message's attachments will be downloaded and included in the output",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['folderMessage'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
let responseData;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
const folderId = decodeOutlookId(
|
||||||
|
this.getNodeParameter('folderId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string,
|
||||||
|
);
|
||||||
|
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', index);
|
||||||
|
const filters = this.getNodeParameter('filtersUI.values', index, {}) as IDataObject;
|
||||||
|
const options = this.getNodeParameter('options', index, {});
|
||||||
|
const output = this.getNodeParameter('output', index) as string;
|
||||||
|
|
||||||
|
if (output === 'fields') {
|
||||||
|
const fields = this.getNodeParameter('fields', index) as string[];
|
||||||
|
|
||||||
|
if (options.downloadAttachments) {
|
||||||
|
fields.push('hasAttachments');
|
||||||
|
}
|
||||||
|
|
||||||
|
qs.$select = fields.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output === 'simple') {
|
||||||
|
qs.$select =
|
||||||
|
'id,conversationId,subject,bodyPreview,from,toRecipients,categories,hasAttachments';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.filterBy === 'search' && filters.search !== '') {
|
||||||
|
qs.$search = `"${filters.search}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.filterBy === 'filters') {
|
||||||
|
const filterString = prepareFilterString(filters);
|
||||||
|
|
||||||
|
if (filterString) {
|
||||||
|
qs.$filter = filterString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `/mailFolders/${folderId}/messages`;
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qs.$top = this.getNodeParameter('limit', index);
|
||||||
|
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||||
|
responseData = responseData.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output === 'simple') {
|
||||||
|
responseData = simplifyOutputMessages(responseData as IDataObject[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let executionData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
|
if (options.downloadAttachments) {
|
||||||
|
const prefix = (options.attachmentsPrefix as string) || 'attachment_';
|
||||||
|
executionData = await downloadAttachments.call(this, responseData as IDataObject, prefix);
|
||||||
|
} else {
|
||||||
|
executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import * as getAll from './getAll.operation';
|
||||||
|
|
||||||
|
export { getAll };
|
||||||
|
|
||||||
|
export const description: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['folderMessage'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Retrieves the messages in a folder',
|
||||||
|
action: 'Get many folder messages',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'getAll',
|
||||||
|
},
|
||||||
|
|
||||||
|
...getAll.description,
|
||||||
|
];
|
|
@ -0,0 +1,29 @@
|
||||||
|
import type { IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { messageRLC } from '../../descriptions';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [messageRLC];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
operation: ['delete'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
await microsoftApiRequest.call(this, 'DELETE', `/messages/${messageId}`);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ success: true }),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
import type {
|
||||||
|
IBinaryKeyData,
|
||||||
|
IDataObject,
|
||||||
|
IExecuteFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { messageFields, simplifyOutputMessages } from '../../helpers/utils';
|
||||||
|
import { downloadAttachments, getMimeContent, microsoftApiRequest } from '../../transport';
|
||||||
|
import { messageRLC } from '../../descriptions';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
messageRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Output',
|
||||||
|
name: 'output',
|
||||||
|
type: 'options',
|
||||||
|
default: 'simple',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Simplified',
|
||||||
|
value: 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Raw',
|
||||||
|
value: 'raw',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Select Included Fields',
|
||||||
|
value: 'fields',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description: 'The fields to add to the output',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
output: ['fields'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: messageFields,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Attachments Prefix',
|
||||||
|
name: 'attachmentsPrefix',
|
||||||
|
type: 'string',
|
||||||
|
default: 'attachment_',
|
||||||
|
description:
|
||||||
|
'Prefix for name of the output fields to put the binary files data in. An index starting from 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0".',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Download Attachments',
|
||||||
|
name: 'downloadAttachments',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description:
|
||||||
|
"Whether the message's attachments will be downloaded and included in the output",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Get MIME Content',
|
||||||
|
name: 'getMimeContent',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
default: { values: { binaryPropertyName: 'data' } },
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Values',
|
||||||
|
name: 'values',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Put Output in Field',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
hint: 'The name of the output field to put the binary file data in',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'File Name',
|
||||||
|
name: 'outputFileName',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'message',
|
||||||
|
default: '',
|
||||||
|
description: 'Optional name of the output file, if not set message ID is used',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
let responseData;
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
const messageId = this.getNodeParameter('messageId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const options = this.getNodeParameter('options', index, {});
|
||||||
|
const output = this.getNodeParameter('output', index) as string;
|
||||||
|
|
||||||
|
if (output === 'fields') {
|
||||||
|
const fields = this.getNodeParameter('fields', index) as string[];
|
||||||
|
|
||||||
|
if (options.downloadAttachments) {
|
||||||
|
fields.push('hasAttachments');
|
||||||
|
}
|
||||||
|
|
||||||
|
qs.$select = fields.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output === 'simple') {
|
||||||
|
qs.$select =
|
||||||
|
'id,conversationId,subject,bodyPreview,from,toRecipients,categories,hasAttachments';
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${messageId}`,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (output === 'simple') {
|
||||||
|
responseData = simplifyOutputMessages([responseData as IDataObject]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let executionData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
|
if (options.downloadAttachments) {
|
||||||
|
const prefix = (options.attachmentsPrefix as string) || 'attachment_';
|
||||||
|
executionData = await downloadAttachments.call(this, responseData as IDataObject, prefix);
|
||||||
|
} else {
|
||||||
|
executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.getMimeContent) {
|
||||||
|
const { binaryPropertyName, outputFileName } = (options.getMimeContent as IDataObject)
|
||||||
|
.values as IDataObject;
|
||||||
|
|
||||||
|
const binary = await getMimeContent.call(
|
||||||
|
this,
|
||||||
|
messageId,
|
||||||
|
binaryPropertyName as string,
|
||||||
|
outputFileName as string,
|
||||||
|
);
|
||||||
|
|
||||||
|
executionData[0].binary = {
|
||||||
|
...(executionData[0].binary || {}),
|
||||||
|
...(binary as IBinaryKeyData),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,311 @@
|
||||||
|
import type {
|
||||||
|
IDataObject,
|
||||||
|
IExecuteFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { messageFields, prepareFilterString, simplifyOutputMessages } from '../../helpers/utils';
|
||||||
|
import {
|
||||||
|
downloadAttachments,
|
||||||
|
microsoftApiRequest,
|
||||||
|
microsoftApiRequestAllItems,
|
||||||
|
} from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { returnAllOrLimit } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
...returnAllOrLimit,
|
||||||
|
{
|
||||||
|
displayName: 'Output',
|
||||||
|
name: 'output',
|
||||||
|
type: 'options',
|
||||||
|
default: 'simple',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Simplified',
|
||||||
|
value: 'simple',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Raw',
|
||||||
|
value: 'raw',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Select Included Fields',
|
||||||
|
value: 'fields',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description: 'The fields to add to the output',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
output: ['fields'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: messageFields,
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName:
|
||||||
|
'Fetching a lot of messages may take a long time. Consider using filters to speed things up',
|
||||||
|
name: 'filtersNotice',
|
||||||
|
type: 'notice',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
returnAll: [true],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filters',
|
||||||
|
name: 'filtersUI',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Filters',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Values',
|
||||||
|
name: 'values',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Filter By',
|
||||||
|
name: 'filterBy',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Filters',
|
||||||
|
value: 'filters',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Search',
|
||||||
|
value: 'search',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'filters',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Search',
|
||||||
|
name: 'search',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g. automation',
|
||||||
|
description:
|
||||||
|
'Only return messages that contains search term. Without specific message properties, the search is carried out on the default search properties of from, subject, and body. <a href="https://docs.microsoft.com/en-us/graph/query-parameters#search-parameter target="_blank">More info</a>.',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
filterBy: ['search'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filters',
|
||||||
|
name: 'filters',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Filter',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
filterBy: ['filters'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Filter Query',
|
||||||
|
name: 'custom',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g. isRead eq false',
|
||||||
|
hint: 'Search query to filter messages. <a href="https://learn.microsoft.com/en-us/graph/filter-query-parameter">More info</a>.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Has Attachments',
|
||||||
|
name: 'hasAttachments',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Folders to Exclude',
|
||||||
|
name: 'foldersToExclude',
|
||||||
|
type: 'multiOptions',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getFolders',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
description:
|
||||||
|
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Folders to Include',
|
||||||
|
name: 'foldersToInclude',
|
||||||
|
type: 'multiOptions',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getFolders',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
description:
|
||||||
|
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Read Status',
|
||||||
|
name: 'readStatus',
|
||||||
|
type: 'options',
|
||||||
|
default: 'unread',
|
||||||
|
hint: 'Filter messages by whether they have been read or not',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
name: 'Unread and read messages',
|
||||||
|
value: 'both',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
name: 'Unread messages only',
|
||||||
|
value: 'unread',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
name: 'Read messages only',
|
||||||
|
value: 'read',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Received After',
|
||||||
|
name: 'receivedAfter',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'Get all messages received after the specified date. In an expression you can set date using string in ISO format or a timestamp in miliseconds.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Received Before',
|
||||||
|
name: 'receivedBefore',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
description:
|
||||||
|
'Get all messages received before the specified date. In an expression you can set date using string in ISO format or a timestamp in miliseconds.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sender',
|
||||||
|
name: 'sender',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Sender name or email to filter by',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Attachments Prefix',
|
||||||
|
name: 'attachmentsPrefix',
|
||||||
|
type: 'string',
|
||||||
|
default: 'attachment_',
|
||||||
|
description:
|
||||||
|
'Prefix for name of the output fields to put the binary files data in. An index starting from 0 will be added. So if name is "attachment_" the first attachment is saved to "attachment_0".',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Download Attachments',
|
||||||
|
name: 'downloadAttachments',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description:
|
||||||
|
"Whether the message's attachments will be downloaded and included in the output",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
let responseData;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', index);
|
||||||
|
const filters = this.getNodeParameter('filtersUI.values', index, {}) as IDataObject;
|
||||||
|
const options = this.getNodeParameter('options', index, {});
|
||||||
|
const output = this.getNodeParameter('output', index) as string;
|
||||||
|
|
||||||
|
if (output === 'fields') {
|
||||||
|
const fields = this.getNodeParameter('fields', index) as string[];
|
||||||
|
|
||||||
|
if (options.downloadAttachments) {
|
||||||
|
fields.push('hasAttachments');
|
||||||
|
}
|
||||||
|
|
||||||
|
qs.$select = fields.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output === 'simple') {
|
||||||
|
qs.$select =
|
||||||
|
'id,conversationId,subject,bodyPreview,from,toRecipients,categories,hasAttachments';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.filterBy === 'search' && filters.search !== '') {
|
||||||
|
qs.$search = `"${filters.search}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filters.filterBy === 'filters') {
|
||||||
|
const filterString = prepareFilterString(filters);
|
||||||
|
|
||||||
|
if (filterString) {
|
||||||
|
qs.$filter = filterString;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = '/messages';
|
||||||
|
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qs.$top = this.getNodeParameter('limit', index);
|
||||||
|
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||||
|
responseData = responseData.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output === 'simple') {
|
||||||
|
responseData = simplifyOutputMessages(responseData as IDataObject[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let executionData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
|
if (options.downloadAttachments) {
|
||||||
|
const prefix = (options.attachmentsPrefix as string) || 'attachment_';
|
||||||
|
executionData = await downloadAttachments.call(this, responseData as IDataObject, prefix);
|
||||||
|
} else {
|
||||||
|
executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,77 @@
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import * as del from './delete.operation';
|
||||||
|
import * as get from './get.operation';
|
||||||
|
import * as getAll from './getAll.operation';
|
||||||
|
import * as move from './move.operation';
|
||||||
|
import * as reply from './reply.operation';
|
||||||
|
import * as send from './send.operation';
|
||||||
|
import * as update from './update.operation';
|
||||||
|
|
||||||
|
export { del as delete, get, getAll, move, reply, send, update };
|
||||||
|
|
||||||
|
export const description: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a message',
|
||||||
|
action: 'Delete a message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Retrieve a single message',
|
||||||
|
action: 'Get a message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'List and search messages',
|
||||||
|
action: 'Get many messages',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Move',
|
||||||
|
value: 'move',
|
||||||
|
description: 'Move a message to a folder',
|
||||||
|
action: 'Move a message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Reply',
|
||||||
|
value: 'reply',
|
||||||
|
description: 'Create a reply to a message',
|
||||||
|
action: 'Reply to a message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Send',
|
||||||
|
value: 'send',
|
||||||
|
description: 'Send a message',
|
||||||
|
action: 'Send a message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update a message',
|
||||||
|
action: 'Update a message',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'send',
|
||||||
|
},
|
||||||
|
|
||||||
|
...del.description,
|
||||||
|
...get.description,
|
||||||
|
...getAll.description,
|
||||||
|
...move.description,
|
||||||
|
...reply.description,
|
||||||
|
...send.description,
|
||||||
|
...update.description,
|
||||||
|
];
|
|
@ -0,0 +1,41 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { folderRLC, messageRLC } from '../../descriptions';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
messageRLC,
|
||||||
|
{ ...folderRLC, displayName: 'Parent Folder' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
operation: ['move'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const destinationId = this.getNodeParameter('folderId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
destinationId,
|
||||||
|
};
|
||||||
|
|
||||||
|
await microsoftApiRequest.call(this, 'POST', `/messages/${messageId}/move`, body);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ success: true }),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,306 @@
|
||||||
|
import type {
|
||||||
|
IBinaryKeyData,
|
||||||
|
IDataObject,
|
||||||
|
IExecuteFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
import { createMessage } from '../../helpers/utils';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { messageRLC } from '../../descriptions';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
messageRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Reply to Sender Only',
|
||||||
|
name: 'replyToSenderOnly',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to reply to the sender only or to the entire list of recipients',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message',
|
||||||
|
name: 'message',
|
||||||
|
// name: 'bodyContent',
|
||||||
|
description: 'Message body content',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
rows: 2,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
replyToSenderOnly: [true],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Attachments',
|
||||||
|
name: 'attachments',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Attachment',
|
||||||
|
default: {},
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'attachments',
|
||||||
|
displayName: 'Attachment',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Input Data Field Name',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g. data',
|
||||||
|
hint: 'The name of the input field containing the binary file data to be attached',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'BCC Recipients',
|
||||||
|
name: 'bccRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of BCC recipients',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CC Recipients',
|
||||||
|
name: 'ccRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of CC recipients',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Headers',
|
||||||
|
name: 'internetMessageHeaders',
|
||||||
|
placeholder: 'Add Header',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'headers',
|
||||||
|
displayName: 'Header',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the header',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value to set for the header',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'From',
|
||||||
|
name: 'from',
|
||||||
|
description:
|
||||||
|
'The owner of the mailbox from which the message is sent. Must correspond to the actual mailbox used.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Importance',
|
||||||
|
name: 'importance',
|
||||||
|
description: 'The importance of the message',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Low',
|
||||||
|
value: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'High',
|
||||||
|
value: 'High',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message Type',
|
||||||
|
name: 'bodyContentType',
|
||||||
|
description: 'Message body content type',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'HTML',
|
||||||
|
value: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Text',
|
||||||
|
value: 'Text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Read Receipt Requested',
|
||||||
|
name: 'isReadReceiptRequested',
|
||||||
|
description: 'Whether a read receipt is requested for the message',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'To',
|
||||||
|
name: 'toRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of recipients',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Reply To',
|
||||||
|
name: 'replyTo',
|
||||||
|
description: 'Email address to use when replying',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Subject',
|
||||||
|
name: 'subject',
|
||||||
|
description: 'The subject of the message',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Save as Draft',
|
||||||
|
name: 'saveAsDraft',
|
||||||
|
description:
|
||||||
|
'Whether to save the message as a draft. If false, the message is sent immediately.',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
operation: ['reply'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number, items: INodeExecutionData[]) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
const replyToSenderOnly = this.getNodeParameter('replyToSenderOnly', index, false) as string;
|
||||||
|
const message = this.getNodeParameter('message', index) as string;
|
||||||
|
const saveAsDraft = this.getNodeParameter('options.saveAsDraft', index, false) as boolean;
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', index, {});
|
||||||
|
|
||||||
|
const body: IDataObject = {};
|
||||||
|
|
||||||
|
let action = 'createReply';
|
||||||
|
|
||||||
|
if (!replyToSenderOnly) {
|
||||||
|
body.comment = message;
|
||||||
|
action = 'createReplyAll';
|
||||||
|
} else {
|
||||||
|
// body.comment = comment;
|
||||||
|
body.message = {} as IDataObject;
|
||||||
|
additionalFields.bodyContent = message;
|
||||||
|
Object.assign(body.message, createMessage(additionalFields));
|
||||||
|
|
||||||
|
delete (body.message as IDataObject).attachments;
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages/${messageId}/${action}`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (additionalFields.attachments) {
|
||||||
|
const attachments = (additionalFields.attachments as IDataObject).attachments as IDataObject[];
|
||||||
|
// // Handle attachments
|
||||||
|
const data = attachments.map((attachment) => {
|
||||||
|
const binaryPropertyName = attachment.binaryPropertyName as string;
|
||||||
|
|
||||||
|
if (items[index].binary === undefined) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'No binary data exists on item!', {
|
||||||
|
itemIndex: index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
items[index].binary &&
|
||||||
|
(items[index].binary as IDataObject)[binaryPropertyName] === undefined
|
||||||
|
) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`No binary data property "${binaryPropertyName}" does not exists on item!`,
|
||||||
|
{ itemIndex: index },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryData = (items[index].binary as IBinaryKeyData)[binaryPropertyName];
|
||||||
|
return {
|
||||||
|
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||||
|
name: binaryData.fileName,
|
||||||
|
contentBytes: binaryData.data,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const attachment of data) {
|
||||||
|
await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages/${responseData.id}/attachments`,
|
||||||
|
attachment,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!saveAsDraft) {
|
||||||
|
await microsoftApiRequest.call(this, 'POST', `/messages/${responseData.id}/send`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,272 @@
|
||||||
|
import type {
|
||||||
|
IBinaryKeyData,
|
||||||
|
IDataObject,
|
||||||
|
IExecuteFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
import { createMessage } from '../../helpers/utils';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'To',
|
||||||
|
name: 'toRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of recipients',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Subject',
|
||||||
|
name: 'subject',
|
||||||
|
description: 'The subject of the message',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message',
|
||||||
|
name: 'bodyContent',
|
||||||
|
description: 'Message body content',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
rows: 2,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Attachments',
|
||||||
|
name: 'attachments',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Attachment',
|
||||||
|
default: {},
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'attachments',
|
||||||
|
displayName: 'Attachment',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Input Data Field Name',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'e.g. data',
|
||||||
|
hint: 'The name of the input field containing the binary file data to be attached',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'BCC Recipients',
|
||||||
|
name: 'bccRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of BCC recipients',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Category Names or IDs',
|
||||||
|
name: 'categories',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description:
|
||||||
|
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCategoriesNames',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CC Recipients',
|
||||||
|
name: 'ccRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of CC recipients',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Headers',
|
||||||
|
name: 'internetMessageHeaders',
|
||||||
|
placeholder: 'Add Header',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'headers',
|
||||||
|
displayName: 'Header',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the header',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value to set for the header',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'From',
|
||||||
|
name: 'from',
|
||||||
|
description:
|
||||||
|
'The owner of the mailbox from which the message is sent. Must correspond to the actual mailbox used.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Importance',
|
||||||
|
name: 'importance',
|
||||||
|
description: 'The importance of the message',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Low',
|
||||||
|
value: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'High',
|
||||||
|
value: 'High',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message Type',
|
||||||
|
name: 'bodyContentType',
|
||||||
|
description: 'Message body content type',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'HTML',
|
||||||
|
value: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Text',
|
||||||
|
value: 'Text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Read Receipt Requested',
|
||||||
|
name: 'isReadReceiptRequested',
|
||||||
|
description: 'Whether a read receipt is requested for the message',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Reply To',
|
||||||
|
name: 'replyTo',
|
||||||
|
description: 'Email address to use when replying',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Save To Sent Items',
|
||||||
|
name: 'saveToSentItems',
|
||||||
|
description: 'Whether to save the message in Sent Items',
|
||||||
|
type: 'boolean',
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
operation: ['send'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number, items: INodeExecutionData[]) {
|
||||||
|
const additionalFields = this.getNodeParameter('additionalFields', index);
|
||||||
|
const toRecipients = this.getNodeParameter('toRecipients', index) as string;
|
||||||
|
const subject = this.getNodeParameter('subject', index) as string;
|
||||||
|
const bodyContent = this.getNodeParameter('bodyContent', index, '') as string;
|
||||||
|
|
||||||
|
additionalFields.subject = subject;
|
||||||
|
additionalFields.bodyContent = bodyContent || ' ';
|
||||||
|
additionalFields.toRecipients = toRecipients;
|
||||||
|
|
||||||
|
const saveToSentItems =
|
||||||
|
additionalFields.saveToSentItems === undefined ? true : additionalFields.saveToSentItems;
|
||||||
|
delete additionalFields.saveToSentItems;
|
||||||
|
|
||||||
|
// Create message object from optional fields
|
||||||
|
const message: IDataObject = createMessage(additionalFields);
|
||||||
|
|
||||||
|
if (additionalFields.attachments) {
|
||||||
|
const attachments = (additionalFields.attachments as IDataObject).attachments as IDataObject[];
|
||||||
|
|
||||||
|
// // Handle attachments
|
||||||
|
message.attachments = attachments.map((attachment) => {
|
||||||
|
const binaryPropertyName = attachment.binaryPropertyName as string;
|
||||||
|
|
||||||
|
if (items[index].binary === undefined) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'No binary data exists on item!', {
|
||||||
|
itemIndex: index,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
items[index].binary &&
|
||||||
|
(items[index].binary as IDataObject)[binaryPropertyName] === undefined
|
||||||
|
) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`No binary data property "${binaryPropertyName}" does not exists on item!`,
|
||||||
|
{ itemIndex: index },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryData = (items[index].binary as IBinaryKeyData)[binaryPropertyName];
|
||||||
|
return {
|
||||||
|
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||||
|
name: binaryData.fileName,
|
||||||
|
contentBytes: binaryData.data,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const body: IDataObject = {
|
||||||
|
message,
|
||||||
|
saveToSentItems,
|
||||||
|
};
|
||||||
|
|
||||||
|
await microsoftApiRequest.call(this, 'POST', '/sendMail', body, {});
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ success: true }),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,224 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
import { createMessage, decodeOutlookId } from '../../helpers/utils';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { folderRLC, messageRLC } from '../../descriptions';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
messageRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Update Fields',
|
||||||
|
name: 'updateFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'BCC Recipients',
|
||||||
|
name: 'bccRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of BCC recipients',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Category Names or IDs',
|
||||||
|
name: 'categories',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description:
|
||||||
|
'Choose from the list, or specify IDs using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsMethod: 'getCategoriesNames',
|
||||||
|
},
|
||||||
|
default: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'CC Recipients',
|
||||||
|
name: 'ccRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of CC recipients',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Custom Headers',
|
||||||
|
name: 'internetMessageHeaders',
|
||||||
|
placeholder: 'Add Header',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'headers',
|
||||||
|
displayName: 'Header',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Name of the header',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'Value to set for the header',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ ...folderRLC, required: false },
|
||||||
|
{
|
||||||
|
displayName: 'Importance',
|
||||||
|
name: 'importance',
|
||||||
|
description: 'The importance of the message',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Low',
|
||||||
|
value: 'Low',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Normal',
|
||||||
|
value: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'High',
|
||||||
|
value: 'High',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'Normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Is Read',
|
||||||
|
name: 'isRead',
|
||||||
|
description: 'Whether the message must be marked as read',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message',
|
||||||
|
name: 'bodyContent',
|
||||||
|
description: 'Message body content',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
rows: 2,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Message Type',
|
||||||
|
name: 'bodyContentType',
|
||||||
|
description: 'Message body content type',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'HTML',
|
||||||
|
value: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Text',
|
||||||
|
value: 'Text',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'html',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Read Receipt Requested',
|
||||||
|
name: 'isReadReceiptRequested',
|
||||||
|
description: 'Whether a read receipt is requested for the message',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'To',
|
||||||
|
name: 'toRecipients',
|
||||||
|
description: 'Comma-separated list of email addresses of recipients',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Reply To',
|
||||||
|
name: 'replyTo',
|
||||||
|
description: 'Email address to use when replying',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Subject',
|
||||||
|
name: 'subject',
|
||||||
|
description: 'The subject of the message',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['message'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
let responseData;
|
||||||
|
|
||||||
|
const messageId = this.getNodeParameter('messageId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const updateFields = this.getNodeParameter('updateFields', index);
|
||||||
|
|
||||||
|
const folderId = decodeOutlookId(
|
||||||
|
this.getNodeParameter('updateFields.folderId', index, '', {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (folderId) {
|
||||||
|
const body: IDataObject = {
|
||||||
|
destinationId: folderId,
|
||||||
|
};
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages/${messageId}/move`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
|
||||||
|
delete updateFields.folderId;
|
||||||
|
|
||||||
|
if (!Object.keys(updateFields).length) {
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const body: IDataObject = createMessage(updateFields);
|
||||||
|
|
||||||
|
if (!Object.keys(body).length) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'No fields to update got specified');
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(this, 'PATCH', `/messages/${messageId}`, body, {});
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
import type {
|
||||||
|
IBinaryKeyData,
|
||||||
|
IDataObject,
|
||||||
|
IExecuteFunctions,
|
||||||
|
INodeExecutionData,
|
||||||
|
INodeProperties,
|
||||||
|
JsonObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { messageRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
messageRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Input Data Field Name',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
hint: 'The name of the input field containing the binary file data to be attached',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: 'data',
|
||||||
|
placeholder: 'e.g. data',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'File Name',
|
||||||
|
name: 'fileName',
|
||||||
|
description:
|
||||||
|
'Filename of the attachment. If not set will the file-name of the binary property be used, if it exists.',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['messageAttachment'],
|
||||||
|
operation: ['add'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number, items: INodeExecutionData[]) {
|
||||||
|
let responseData;
|
||||||
|
|
||||||
|
const messageId = this.getNodeParameter('messageId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', 0);
|
||||||
|
const options = this.getNodeParameter('options', index);
|
||||||
|
|
||||||
|
if (items[index].binary === undefined) {
|
||||||
|
throw new NodeOperationError(this.getNode(), 'No binary data exists on item!');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
items[index].binary &&
|
||||||
|
(items[index].binary as IDataObject)[binaryPropertyName] === undefined
|
||||||
|
) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
`No binary data property "${binaryPropertyName}" does not exists on item!`,
|
||||||
|
{ itemIndex: index },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binaryData = (items[index].binary as IBinaryKeyData)[binaryPropertyName];
|
||||||
|
const dataBuffer = await this.helpers.getBinaryDataBuffer(index, binaryPropertyName);
|
||||||
|
|
||||||
|
const fileName = options.fileName === undefined ? binaryData.fileName : options.fileName;
|
||||||
|
|
||||||
|
if (!fileName) {
|
||||||
|
throw new NodeOperationError(
|
||||||
|
this.getNode(),
|
||||||
|
'File name is not set. It has either to be set via "Additional Fields" or has to be set on the binary property!',
|
||||||
|
{ itemIndex: index },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the file is over 3MB big
|
||||||
|
if (dataBuffer.length > 3e6) {
|
||||||
|
// Maximum chunk size is 4MB
|
||||||
|
const chunkSize = 4e6;
|
||||||
|
const body: IDataObject = {
|
||||||
|
AttachmentItem: {
|
||||||
|
attachmentType: 'file',
|
||||||
|
name: fileName,
|
||||||
|
size: dataBuffer.length,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create upload session
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages/${messageId}/attachments/createUploadSession`,
|
||||||
|
body,
|
||||||
|
);
|
||||||
|
const uploadUrl = responseData.uploadUrl;
|
||||||
|
|
||||||
|
if (uploadUrl === undefined) {
|
||||||
|
throw new NodeApiError(this.getNode(), responseData as JsonObject, {
|
||||||
|
message: 'Failed to get upload session',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let bytesUploaded = 0; bytesUploaded < dataBuffer.length; bytesUploaded += chunkSize) {
|
||||||
|
// Upload the file chunk by chunk
|
||||||
|
const nextChunk = Math.min(bytesUploaded + chunkSize, dataBuffer.length);
|
||||||
|
const contentRange = `bytes ${bytesUploaded}-${nextChunk - 1}/${dataBuffer.length}`;
|
||||||
|
|
||||||
|
const data = dataBuffer.subarray(bytesUploaded, nextChunk);
|
||||||
|
|
||||||
|
responseData = await this.helpers.request(uploadUrl, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/octet-stream',
|
||||||
|
'Content-Length': data.length,
|
||||||
|
'Content-Range': contentRange,
|
||||||
|
},
|
||||||
|
body: data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const body: IDataObject = {
|
||||||
|
'@odata.type': '#microsoft.graph.fileAttachment',
|
||||||
|
name: fileName,
|
||||||
|
contentBytes: binaryData.data,
|
||||||
|
};
|
||||||
|
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'POST',
|
||||||
|
`/messages/${messageId}/attachments`,
|
||||||
|
body,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ success: true }),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
import type { IExecuteFunctions, INodeExecutionData, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { attachmentRLC, messageRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
messageRLC,
|
||||||
|
attachmentRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Put Output in Field',
|
||||||
|
name: 'binaryPropertyName',
|
||||||
|
hint: 'The name of the output field to put the binary file data in',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: 'data',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['messageAttachment'],
|
||||||
|
operation: ['download'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number, items: INodeExecutionData[]) {
|
||||||
|
const messageId = this.getNodeParameter('messageId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const attachmentId = this.getNodeParameter('attachmentId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', index);
|
||||||
|
|
||||||
|
// Get attachment details first
|
||||||
|
const attachmentDetails = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${messageId}/attachments/${attachmentId}`,
|
||||||
|
undefined,
|
||||||
|
{ $select: 'id,name,contentType' },
|
||||||
|
);
|
||||||
|
|
||||||
|
let mimeType: string | undefined;
|
||||||
|
if (attachmentDetails.contentType) {
|
||||||
|
mimeType = attachmentDetails.contentType;
|
||||||
|
}
|
||||||
|
const fileName = attachmentDetails.name;
|
||||||
|
|
||||||
|
const response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${messageId}/attachments/${attachmentId}/$value`,
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
{ encoding: null, resolveWithFullResponse: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const newItem: INodeExecutionData = {
|
||||||
|
json: items[index].json,
|
||||||
|
binary: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (items[index].binary !== undefined) {
|
||||||
|
// Create a shallow copy of the binary data so that the old
|
||||||
|
// data references which do not get changed still stay behind
|
||||||
|
// but the incoming data does not get changed.
|
||||||
|
Object.assign(newItem.binary!, items[index].binary);
|
||||||
|
}
|
||||||
|
|
||||||
|
items[index] = newItem;
|
||||||
|
const data = Buffer.from(response.body as string, 'utf8');
|
||||||
|
items[index].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(
|
||||||
|
data as unknown as Buffer,
|
||||||
|
fileName as string,
|
||||||
|
mimeType,
|
||||||
|
);
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { attachmentRLC, messageRLC } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
messageRLC,
|
||||||
|
attachmentRLC,
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description: 'The fields to add to the output',
|
||||||
|
default: [],
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'contentType',
|
||||||
|
value: 'contentType',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'isInline',
|
||||||
|
value: 'isInline',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lastModifiedDateTime',
|
||||||
|
value: 'lastModifiedDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
name: 'name',
|
||||||
|
value: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
name: 'size',
|
||||||
|
value: 'size',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['messageAttachment'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
const qs: IDataObject = {};
|
||||||
|
|
||||||
|
const messageId = this.getNodeParameter('messageId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const attachmentId = this.getNodeParameter('attachmentId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const options = this.getNodeParameter('options', index);
|
||||||
|
|
||||||
|
// Have sane defaults so we don't fetch attachment data in this operation
|
||||||
|
qs.$select = 'id,lastModifiedDateTime,name,contentType,size,isInline';
|
||||||
|
|
||||||
|
if (options.fields && (options.fields as string[]).length) {
|
||||||
|
qs.$select = (options.fields as string[]).map((field) => field.trim()).join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${messageId}/attachments/${attachmentId}`,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
import type { IDataObject, IExecuteFunctions, INodeProperties } from 'n8n-workflow';
|
||||||
|
import { microsoftApiRequest, microsoftApiRequestAllItems } from '../../transport';
|
||||||
|
import { updateDisplayOptions } from '@utils/utilities';
|
||||||
|
import { messageRLC, returnAllOrLimit } from '../../descriptions';
|
||||||
|
|
||||||
|
export const properties: INodeProperties[] = [
|
||||||
|
messageRLC,
|
||||||
|
...returnAllOrLimit,
|
||||||
|
{
|
||||||
|
displayName: 'Options',
|
||||||
|
name: 'options',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Option',
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Fields',
|
||||||
|
name: 'fields',
|
||||||
|
type: 'multiOptions',
|
||||||
|
description: 'The fields to add to the output',
|
||||||
|
default: [],
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'contentType',
|
||||||
|
value: 'contentType',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'isInline',
|
||||||
|
value: 'isInline',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'lastModifiedDateTime',
|
||||||
|
value: 'lastModifiedDateTime',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
name: 'name',
|
||||||
|
value: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
|
||||||
|
name: 'size',
|
||||||
|
value: 'size',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const displayOptions = {
|
||||||
|
show: {
|
||||||
|
resource: ['messageAttachment'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const description = updateDisplayOptions(displayOptions, properties);
|
||||||
|
|
||||||
|
export async function execute(this: IExecuteFunctions, index: number) {
|
||||||
|
let responseData;
|
||||||
|
const qs = {} as IDataObject;
|
||||||
|
|
||||||
|
const messageId = this.getNodeParameter('messageId', index, undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
const returnAll = this.getNodeParameter('returnAll', index);
|
||||||
|
const options = this.getNodeParameter('options', index);
|
||||||
|
|
||||||
|
// Have sane defaults so we don't fetch attachment data in this operation
|
||||||
|
qs.$select = 'id,lastModifiedDateTime,name,contentType,size,isInline';
|
||||||
|
|
||||||
|
if (options.fields && (options.fields as string[]).length) {
|
||||||
|
qs.$select = (options.fields as string[]).map((field) => field.trim()).join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
const endpoint = `/messages/${messageId}/attachments`;
|
||||||
|
if (returnAll) {
|
||||||
|
responseData = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
endpoint,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qs.$top = this.getNodeParameter('limit', index);
|
||||||
|
responseData = await microsoftApiRequest.call(this, 'GET', endpoint, undefined, qs);
|
||||||
|
responseData = responseData.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray(responseData as IDataObject),
|
||||||
|
{ itemData: { item: index } },
|
||||||
|
);
|
||||||
|
|
||||||
|
return executionData;
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
import * as add from './add.operation';
|
||||||
|
import * as download from './download.operation';
|
||||||
|
import * as get from './get.operation';
|
||||||
|
import * as getAll from './getAll.operation';
|
||||||
|
|
||||||
|
export { add, download, get, getAll };
|
||||||
|
|
||||||
|
export const description: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['messageAttachment'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Add',
|
||||||
|
value: 'add',
|
||||||
|
description: 'Add an attachment to a message',
|
||||||
|
action: 'Add an attachment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Download',
|
||||||
|
value: 'download',
|
||||||
|
description: 'Download an attachment from a message',
|
||||||
|
action: 'Download an attachment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Retrieve information about an attachment of a message',
|
||||||
|
action: 'Get an attachment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Retrieve information about the attachments of a message',
|
||||||
|
action: 'Get many attachments',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'add',
|
||||||
|
},
|
||||||
|
...add.description,
|
||||||
|
...download.description,
|
||||||
|
...get.description,
|
||||||
|
...getAll.description,
|
||||||
|
];
|
|
@ -0,0 +1,82 @@
|
||||||
|
/* eslint-disable n8n-nodes-base/node-filename-against-convention */
|
||||||
|
import type { INodeTypeDescription } from 'n8n-workflow';
|
||||||
|
import * as calendar from './calendar';
|
||||||
|
import * as contact from './contact';
|
||||||
|
import * as draft from './draft';
|
||||||
|
import * as event from './event';
|
||||||
|
import * as folder from './folder';
|
||||||
|
import * as folderMessage from './folderMessage';
|
||||||
|
import * as message from './message';
|
||||||
|
import * as messageAttachment from './messageAttachment';
|
||||||
|
|
||||||
|
export const description: INodeTypeDescription = {
|
||||||
|
displayName: 'Microsoft Outlook',
|
||||||
|
name: 'microsoftOutlook',
|
||||||
|
group: ['transform'],
|
||||||
|
icon: 'file:outlook.svg',
|
||||||
|
version: 2,
|
||||||
|
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||||
|
description: 'Consume Microsoft Outlook API',
|
||||||
|
defaults: {
|
||||||
|
name: 'Microsoft Outlook',
|
||||||
|
},
|
||||||
|
inputs: ['main'],
|
||||||
|
outputs: ['main'],
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'microsoftOutlookOAuth2Api',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
displayName: 'Resource',
|
||||||
|
name: 'resource',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
default: 'message',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Calendar',
|
||||||
|
value: 'calendar',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Contact',
|
||||||
|
value: 'contact',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Draft',
|
||||||
|
value: 'draft',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Event',
|
||||||
|
value: 'event',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Folder',
|
||||||
|
value: 'folder',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Folder Message',
|
||||||
|
value: 'folderMessage',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Message',
|
||||||
|
value: 'message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Message Attachment',
|
||||||
|
value: 'messageAttachment',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
...calendar.description,
|
||||||
|
...contact.description,
|
||||||
|
...draft.description,
|
||||||
|
...event.description,
|
||||||
|
...folder.description,
|
||||||
|
...folderMessage.description,
|
||||||
|
...message.description,
|
||||||
|
...messageAttachment.description,
|
||||||
|
],
|
||||||
|
};
|
|
@ -0,0 +1,14 @@
|
||||||
|
import type { AllEntities } from 'n8n-workflow';
|
||||||
|
|
||||||
|
type NodeMap = {
|
||||||
|
calendar: 'create' | 'delete' | 'get' | 'getAll' | 'update';
|
||||||
|
contact: 'create' | 'delete' | 'get' | 'getAll' | 'update';
|
||||||
|
draft: 'create' | 'delete' | 'get' | 'send' | 'update';
|
||||||
|
event: 'create' | 'delete' | 'get' | 'getAll' | 'update';
|
||||||
|
folder: 'create' | 'delete' | 'get' | 'getAll' | 'update';
|
||||||
|
folderMessage: 'getAll';
|
||||||
|
message: 'delete' | 'get' | 'getAll' | 'move' | 'update' | 'send' | 'reply';
|
||||||
|
messageAttachment: 'add' | 'download' | 'getAll' | 'get';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type MicrosoftOutlook = AllEntities<NodeMap>;
|
|
@ -0,0 +1,84 @@
|
||||||
|
import type { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow';
|
||||||
|
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import type { MicrosoftOutlook } from './node.type';
|
||||||
|
import * as calendar from './calendar';
|
||||||
|
import * as contact from './contact';
|
||||||
|
import * as draft from './draft';
|
||||||
|
import * as event from './event';
|
||||||
|
import * as folder from './folder';
|
||||||
|
import * as folderMessage from './folderMessage';
|
||||||
|
import * as message from './message';
|
||||||
|
import * as messageAttachment from './messageAttachment';
|
||||||
|
|
||||||
|
export async function router(this: IExecuteFunctions) {
|
||||||
|
const items = this.getInputData();
|
||||||
|
const returnData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
|
const resource = this.getNodeParameter<MicrosoftOutlook>('resource', 0) as string;
|
||||||
|
const operation = this.getNodeParameter('operation', 0);
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
|
||||||
|
const microsoftOutlook = {
|
||||||
|
resource,
|
||||||
|
operation,
|
||||||
|
} as MicrosoftOutlook;
|
||||||
|
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
try {
|
||||||
|
switch (microsoftOutlook.resource) {
|
||||||
|
case 'calendar':
|
||||||
|
responseData = await calendar[microsoftOutlook.operation].execute.call(this, i);
|
||||||
|
break;
|
||||||
|
case 'contact':
|
||||||
|
responseData = await contact[microsoftOutlook.operation].execute.call(this, i);
|
||||||
|
break;
|
||||||
|
case 'draft':
|
||||||
|
responseData = await draft[microsoftOutlook.operation].execute.call(this, i, items);
|
||||||
|
break;
|
||||||
|
case 'event':
|
||||||
|
responseData = await event[microsoftOutlook.operation].execute.call(this, i);
|
||||||
|
break;
|
||||||
|
case 'folder':
|
||||||
|
responseData = await folder[microsoftOutlook.operation].execute.call(this, i);
|
||||||
|
break;
|
||||||
|
case 'folderMessage':
|
||||||
|
responseData = await folderMessage[microsoftOutlook.operation].execute.call(this, i);
|
||||||
|
break;
|
||||||
|
case 'message':
|
||||||
|
responseData = await message[microsoftOutlook.operation].execute.call(this, i, items);
|
||||||
|
break;
|
||||||
|
case 'messageAttachment':
|
||||||
|
responseData = await messageAttachment[microsoftOutlook.operation].execute.call(
|
||||||
|
this,
|
||||||
|
i,
|
||||||
|
items,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NodeOperationError(this.getNode(), `The resource "${resource}" is not known`);
|
||||||
|
}
|
||||||
|
|
||||||
|
returnData.push(...responseData);
|
||||||
|
} catch (error) {
|
||||||
|
if (this.continueOnFail()) {
|
||||||
|
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ error: error.message }),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionErrorData);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
//NodeApiError will be missing the itemIndex, add it
|
||||||
|
if (error instanceof NodeApiError && error?.context?.itemIndex === undefined) {
|
||||||
|
if (error.context === undefined) {
|
||||||
|
error.context = {};
|
||||||
|
}
|
||||||
|
error.context.itemIndex = i;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [returnData];
|
||||||
|
}
|
|
@ -0,0 +1,394 @@
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const returnAllOrLimit: 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,
|
||||||
|
},
|
||||||
|
default: 100,
|
||||||
|
description: 'Max number of results to return',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const folderFields = [
|
||||||
|
{
|
||||||
|
name: 'Child Folder Count',
|
||||||
|
value: 'childFolderCount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Display Name',
|
||||||
|
value: 'displayName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Is Hidden',
|
||||||
|
value: 'isHidden',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Parent Folder ID',
|
||||||
|
value: 'parentFolderId',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Total Item Count',
|
||||||
|
value: 'totalItemCount',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Unread Item Count',
|
||||||
|
value: 'unreadItemCount',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const contactFields: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Assistant Name',
|
||||||
|
name: 'assistantName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: "The name of the contact's assistant",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Birthday',
|
||||||
|
name: 'birthday',
|
||||||
|
type: 'dateTime',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Business Address',
|
||||||
|
name: 'businessAddress',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Address',
|
||||||
|
default: {
|
||||||
|
values: { sity: '', street: '', postalCode: '', countryOrRegion: '', state: '' },
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Address',
|
||||||
|
name: 'values',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'City',
|
||||||
|
name: 'city',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Country/Region',
|
||||||
|
name: 'countryOrRegion',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Postal Code',
|
||||||
|
name: 'postalCode',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'State',
|
||||||
|
name: 'state',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Street',
|
||||||
|
name: 'street',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Business Home Page',
|
||||||
|
name: 'businessHomePage',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Business Phones',
|
||||||
|
name: 'businessPhones',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Comma-separated list of business phone numbers',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Categories',
|
||||||
|
name: 'categories',
|
||||||
|
description: 'Comma-separated list of categories associated with the contact',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Children',
|
||||||
|
name: 'children',
|
||||||
|
description: "Comma-separated list of names of the contact's children",
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Company Name',
|
||||||
|
name: 'companyName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Department',
|
||||||
|
name: 'department',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Display Name',
|
||||||
|
name: 'displayName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Email Address',
|
||||||
|
name: 'emailAddresses',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Email',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'values',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Address',
|
||||||
|
name: 'address',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'File As',
|
||||||
|
name: 'fileAs',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: 'The name the contact is filed under',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Home Address',
|
||||||
|
name: 'homeAddress',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Address',
|
||||||
|
default: {
|
||||||
|
values: { sity: '', street: '', postalCode: '', countryOrRegion: '', state: '' },
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Address',
|
||||||
|
name: 'values',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'City',
|
||||||
|
name: 'city',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Country/Region',
|
||||||
|
name: 'countryOrRegion',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Postal Code',
|
||||||
|
name: 'postalCode',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'State',
|
||||||
|
name: 'state',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Street',
|
||||||
|
name: 'street',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Home Phones',
|
||||||
|
name: 'homePhones',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
hint: 'Multiple phones can be added separated by ,',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Instant Messaging Addresses',
|
||||||
|
name: 'imAddresses',
|
||||||
|
description: "The contact's instant messaging (IM) addresses",
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
hint: 'Multiple addresses can be added separated by ,',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Initials',
|
||||||
|
name: 'initials',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Job Title',
|
||||||
|
name: 'jobTitle',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Manager',
|
||||||
|
name: 'manager',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
description: "The name of the contact's manager",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Middle Name',
|
||||||
|
name: 'middleName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Mobile Phone',
|
||||||
|
name: 'mobilePhone',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Name',
|
||||||
|
name: 'givenName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'/operation': ['update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Nickname',
|
||||||
|
name: 'nickName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Office Location',
|
||||||
|
name: 'officeLocation',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Other Address',
|
||||||
|
name: 'otherAddress',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
placeholder: 'Add Address',
|
||||||
|
default: {
|
||||||
|
values: { sity: '', street: '', postalCode: '', countryOrRegion: '', state: '' },
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Address',
|
||||||
|
name: 'values',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'City',
|
||||||
|
name: 'city',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Country/Region',
|
||||||
|
name: 'countryOrRegion',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Postal Code',
|
||||||
|
name: 'postalCode',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'State',
|
||||||
|
name: 'state',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Street',
|
||||||
|
name: 'street',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Personal Notes',
|
||||||
|
name: 'personalNotes',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
typeOptions: {
|
||||||
|
alwaysOpenEditWindow: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Profession',
|
||||||
|
name: 'profession',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Spouse Name',
|
||||||
|
name: 'spouseName',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Surname',
|
||||||
|
name: 'surname',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Title',
|
||||||
|
name: 'title',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from './rlc.description';
|
||||||
|
export * from './common.descriptions';
|
|
@ -0,0 +1,229 @@
|
||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const calendarRLC: INodeProperties = {
|
||||||
|
displayName: 'Calendar',
|
||||||
|
name: 'calendarId',
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: { mode: 'list', value: '' },
|
||||||
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a calendar...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchCalendars',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. AAAkAAAhAAA0BBc5LLLwOOOtNNNkZS05Nz...',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const contactRLC: INodeProperties = {
|
||||||
|
displayName: 'Contact',
|
||||||
|
name: 'contactId',
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: { mode: 'list', value: '' },
|
||||||
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a contact...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchContacts',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. AAAkAAAhAAA0BBc5LLLwOOOtNNNkZS05Nz...',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const draftRLC: INodeProperties = {
|
||||||
|
displayName: 'Draft',
|
||||||
|
name: 'draftId',
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: { mode: 'list', value: '' },
|
||||||
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a draft...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchDrafts',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. AAAkAAAhAAA0BBc5LLLwOOOtNNNkZS05Nz...',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const messageRLC: INodeProperties = {
|
||||||
|
displayName: 'Message',
|
||||||
|
name: 'messageId',
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: { mode: 'list', value: '' },
|
||||||
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a message...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchMessages',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. AAAkAAAhAAA0BBc5LLLwOOOtNNNkZS05Nz...',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const eventRLC: INodeProperties = {
|
||||||
|
displayName: 'Event',
|
||||||
|
name: 'eventId',
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: { mode: 'list', value: '' },
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['calendarId.value'],
|
||||||
|
},
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a event...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchEvents',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Link',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. https://outlook.office365.com/calendar/item/AAMkADlhOTA0M...UAAA%3D',
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex:
|
||||||
|
'https:\\/\\/outlook\\.office365\\.com\\/calendar\\/item\\/([A-Za-z0-9%]+)(?:\\/.*|)',
|
||||||
|
},
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex:
|
||||||
|
'https:\\/\\/outlook\\.office365\\.com\\/calendar\\/item\\/([A-Za-z0-9%]+)(?:\\/.*|)',
|
||||||
|
errorMessage: 'Not a valid Outlook Event URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. AAAkAAAhAAA0BBc5LLLwOOOtNNNkZS05Nz...',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const folderRLC: INodeProperties = {
|
||||||
|
displayName: 'Folder',
|
||||||
|
name: 'folderId',
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: { mode: 'list', value: '' },
|
||||||
|
required: true,
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a folder...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchFolders',
|
||||||
|
searchable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Link',
|
||||||
|
name: 'url',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. https://outlook.office365.com/mail/AAMkADlhOT...AAA%3D',
|
||||||
|
extractValue: {
|
||||||
|
type: 'regex',
|
||||||
|
regex: 'https:\\/\\/outlook\\.office365\\.com\\/mail\\/([A-Za-z0-9%]+)(?:\\/.*|)',
|
||||||
|
},
|
||||||
|
validation: [
|
||||||
|
{
|
||||||
|
type: 'regex',
|
||||||
|
properties: {
|
||||||
|
regex: 'https:\\/\\/outlook\\.office365\\.com\\/mail\\/([A-Za-z0-9%]+)(?:\\/.*|)',
|
||||||
|
errorMessage: 'Not a valid Outlook Folder URL',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. AAAkAAAhAAA0BBc5LLLwOOOtNNNkZS05Nz...',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const attachmentRLC: INodeProperties = {
|
||||||
|
displayName: 'Attachment',
|
||||||
|
name: 'attachmentId',
|
||||||
|
type: 'resourceLocator',
|
||||||
|
default: { mode: 'list', value: '' },
|
||||||
|
required: true,
|
||||||
|
typeOptions: {
|
||||||
|
loadOptionsDependsOn: ['messageId.value'],
|
||||||
|
},
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
placeholder: 'Select a attachment...',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'searchAttachments',
|
||||||
|
searchable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'ID',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'e.g. AAAkAAAhAAA0BBc5LLLwOOOtNNNkZS05Nz...',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
302
packages/nodes-base/nodes/Microsoft/Outlook/v2/helpers/utils.ts
Normal file
302
packages/nodes-base/nodes/Microsoft/Outlook/v2/helpers/utils.ts
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
import type {
|
||||||
|
IDataObject,
|
||||||
|
IExecuteFunctions,
|
||||||
|
IExecuteSingleFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
JsonObject,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { jsonParse, NodeApiError } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export const messageFields = [
|
||||||
|
'bccRecipients',
|
||||||
|
'body',
|
||||||
|
'bodyPreview',
|
||||||
|
'categories',
|
||||||
|
'ccRecipients',
|
||||||
|
'changeKey',
|
||||||
|
'conversationId',
|
||||||
|
'createdDateTime',
|
||||||
|
'flag',
|
||||||
|
'from',
|
||||||
|
'hasAttachments',
|
||||||
|
'importance',
|
||||||
|
'inferenceClassification',
|
||||||
|
'internetMessageId',
|
||||||
|
'isDeliveryReceiptRequested',
|
||||||
|
'isDraft',
|
||||||
|
'isRead',
|
||||||
|
'isReadReceiptRequested',
|
||||||
|
'lastModifiedDateTime',
|
||||||
|
'parentFolderId',
|
||||||
|
'receivedDateTime',
|
||||||
|
'replyTo',
|
||||||
|
'sender',
|
||||||
|
'sentDateTime',
|
||||||
|
'subject',
|
||||||
|
'toRecipients',
|
||||||
|
'webLink',
|
||||||
|
].map((field) => ({ name: field, value: field }));
|
||||||
|
|
||||||
|
export const eventfields = [
|
||||||
|
'allowNewTimeProposals',
|
||||||
|
'attendees',
|
||||||
|
'body',
|
||||||
|
'bodyPreview',
|
||||||
|
'categories',
|
||||||
|
'changeKey',
|
||||||
|
'createdDateTime',
|
||||||
|
'end',
|
||||||
|
'hasAttachments',
|
||||||
|
'hideAttendees',
|
||||||
|
'iCalUId',
|
||||||
|
'importance',
|
||||||
|
'isAllDay',
|
||||||
|
'isCancelled',
|
||||||
|
'isDraft',
|
||||||
|
'isOnlineMeeting',
|
||||||
|
'isOrganizer',
|
||||||
|
'isReminderOn',
|
||||||
|
'lastModifiedDateTime',
|
||||||
|
'location',
|
||||||
|
'locations',
|
||||||
|
'onlineMeeting',
|
||||||
|
'onlineMeetingProvider',
|
||||||
|
'onlineMeetingUrl',
|
||||||
|
'organizer',
|
||||||
|
'originalEndTimeZone',
|
||||||
|
'originalStartTimeZone',
|
||||||
|
'recurrence',
|
||||||
|
'reminderMinutesBeforeStart',
|
||||||
|
'responseRequested',
|
||||||
|
'responseStatus',
|
||||||
|
'sensitivity',
|
||||||
|
'seriesMasterId',
|
||||||
|
'showAs',
|
||||||
|
'start',
|
||||||
|
'subject',
|
||||||
|
'transactionId',
|
||||||
|
'type',
|
||||||
|
'webLink',
|
||||||
|
].map((field) => ({ name: field, value: field }));
|
||||||
|
|
||||||
|
export const contactFields = [
|
||||||
|
'createdDateTime',
|
||||||
|
'lastModifiedDateTime',
|
||||||
|
'changeKey',
|
||||||
|
'categories',
|
||||||
|
'parentFolderId',
|
||||||
|
'birthday',
|
||||||
|
'fileAs',
|
||||||
|
'displayName',
|
||||||
|
'givenName',
|
||||||
|
'initials',
|
||||||
|
'middleName',
|
||||||
|
'nickName',
|
||||||
|
'surname',
|
||||||
|
'title',
|
||||||
|
'yomiGivenName',
|
||||||
|
'yomiSurname',
|
||||||
|
'yomiCompanyName',
|
||||||
|
'generation',
|
||||||
|
'imAddresses',
|
||||||
|
'jobTitle',
|
||||||
|
'companyName',
|
||||||
|
'department',
|
||||||
|
'officeLocation',
|
||||||
|
'profession',
|
||||||
|
'businessHomePage',
|
||||||
|
'assistantName',
|
||||||
|
'manager',
|
||||||
|
'homePhones',
|
||||||
|
'mobilePhone',
|
||||||
|
'businessPhones',
|
||||||
|
'spouseName',
|
||||||
|
'personalNotes',
|
||||||
|
'children',
|
||||||
|
'emailAddresses',
|
||||||
|
'homeAddress',
|
||||||
|
'businessAddress',
|
||||||
|
'otherAddress',
|
||||||
|
].map((field) => ({ name: field, value: field }));
|
||||||
|
|
||||||
|
export function makeRecipient(email: string) {
|
||||||
|
return {
|
||||||
|
emailAddress: {
|
||||||
|
address: email,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createMessage(fields: IDataObject) {
|
||||||
|
const message: IDataObject = {};
|
||||||
|
|
||||||
|
// Create body object
|
||||||
|
if (fields.bodyContent || fields.bodyContentType) {
|
||||||
|
const bodyObject = {
|
||||||
|
content: fields.bodyContent,
|
||||||
|
contentType: fields.bodyContentType,
|
||||||
|
};
|
||||||
|
|
||||||
|
message.body = bodyObject;
|
||||||
|
delete fields.bodyContent;
|
||||||
|
delete fields.bodyContentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle custom headers
|
||||||
|
if (
|
||||||
|
'internetMessageHeaders' in fields &&
|
||||||
|
'headers' in (fields.internetMessageHeaders as IDataObject)
|
||||||
|
) {
|
||||||
|
fields.internetMessageHeaders = (fields.internetMessageHeaders as IDataObject).headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(fields)) {
|
||||||
|
if (['bccRecipients', 'ccRecipients', 'replyTo', 'sender', 'toRecipients'].includes(key)) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
message[key] = (value as string[]).map((email) => makeRecipient(email));
|
||||||
|
} else if (typeof value === 'string') {
|
||||||
|
message[key] = value.split(',').map((recipient: string) => makeRecipient(recipient.trim()));
|
||||||
|
} else {
|
||||||
|
throw new Error(`The "${key}" field must be a string or an array of strings`);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['from', 'sender'].includes(key)) {
|
||||||
|
if (value) {
|
||||||
|
message[key] = makeRecipient(value as string);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
message[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function simplifyOutputMessages(data: IDataObject[]) {
|
||||||
|
return data.map((item: IDataObject) => {
|
||||||
|
return {
|
||||||
|
id: item.id,
|
||||||
|
conversationId: item.conversationId,
|
||||||
|
subject: item.subject,
|
||||||
|
bodyPreview: item.bodyPreview,
|
||||||
|
from: ((item.from as IDataObject)?.emailAddress as IDataObject)?.address,
|
||||||
|
to: (item.toRecipients as IDataObject[]).map(
|
||||||
|
(recipient: IDataObject) => (recipient.emailAddress as IDataObject)?.address,
|
||||||
|
),
|
||||||
|
categories: item.categories,
|
||||||
|
hasAttachments: item.hasAttachments,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareContactFields(fields: IDataObject) {
|
||||||
|
const returnData: IDataObject = {};
|
||||||
|
|
||||||
|
const typeStringCollection = [
|
||||||
|
'businessPhones',
|
||||||
|
'categories',
|
||||||
|
'children',
|
||||||
|
'homePhones',
|
||||||
|
'imAddresses',
|
||||||
|
];
|
||||||
|
const typeValuesToExtract = ['businessAddress', 'emailAddresses', 'homePhones', 'otherAddress'];
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(fields)) {
|
||||||
|
if (value === undefined || value === '') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeStringCollection.includes(key) && !Array.isArray(value)) {
|
||||||
|
returnData[key] = (value as string).split(',').map((item) => item.trim());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeValuesToExtract.includes(key)) {
|
||||||
|
if ((value as IDataObject).values === undefined) continue;
|
||||||
|
returnData[key] = (value as IDataObject).values;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
returnData[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareFilterString(filters: IDataObject) {
|
||||||
|
const selectedFilters = filters.filters as IDataObject;
|
||||||
|
const filterString: string[] = [];
|
||||||
|
|
||||||
|
if (selectedFilters.foldersToInclude) {
|
||||||
|
const folders = (selectedFilters.foldersToInclude as string[])
|
||||||
|
.filter((folder) => folder !== '')
|
||||||
|
.map((folder) => `parentFolderId eq '${folder}'`)
|
||||||
|
.join(' or ');
|
||||||
|
|
||||||
|
filterString.push(folders);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedFilters.foldersToExclude) {
|
||||||
|
for (const folder of selectedFilters.foldersToExclude as string[]) {
|
||||||
|
filterString.push(`parentFolderId ne '${folder}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedFilters.sender) {
|
||||||
|
const sender = selectedFilters.sender as string;
|
||||||
|
const byMailAddress = `from/emailAddress/address eq '${sender}'`;
|
||||||
|
const byName = `from/emailAddress/name eq '${sender}'`;
|
||||||
|
filterString.push(`(${byMailAddress} or ${byName})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedFilters.hasAttachments) {
|
||||||
|
filterString.push(`hasAttachments eq ${selectedFilters.hasAttachments}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedFilters.readStatus && selectedFilters.readStatus !== 'both') {
|
||||||
|
filterString.push(`isRead eq ${selectedFilters.readStatus === 'read'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedFilters.receivedAfter) {
|
||||||
|
filterString.push(`receivedDateTime ge ${selectedFilters.receivedAfter}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedFilters.receivedBefore) {
|
||||||
|
filterString.push(`receivedDateTime le ${selectedFilters.receivedBefore}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedFilters.custom) {
|
||||||
|
filterString.push(selectedFilters.custom as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
return filterString.length ? filterString.join(' and ') : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareApiError(
|
||||||
|
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
|
||||||
|
error: IDataObject,
|
||||||
|
itemIndex = 0,
|
||||||
|
) {
|
||||||
|
const [httpCode, err, message] = (error.description as string).split(' - ');
|
||||||
|
const json = jsonParse(err);
|
||||||
|
return new NodeApiError(this.getNode(), json as JsonObject, {
|
||||||
|
itemIndex,
|
||||||
|
httpCode,
|
||||||
|
//In UI we are replacing some of the field names to make them more user friendly, updating error message to reflect that
|
||||||
|
message: message
|
||||||
|
.replace(/toRecipients/g, 'toRecipients (To)')
|
||||||
|
.replace(/bodyContent/g, 'bodyContent (Message)')
|
||||||
|
.replace(/bodyContentType/g, 'bodyContentType (Message Type)'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export const encodeOutlookId = (id: string) => {
|
||||||
|
return id.replace(/-/g, '%2F').replace(/=/g, '%3D').replace(/\+/g, '%2B');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const decodeOutlookId = (id: string) => {
|
||||||
|
return id.replace(/%2F/g, '-').replace(/%3D/g, '=').replace(/%2B/g, '+');
|
||||||
|
};
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * as loadOptions from './loadOptions';
|
||||||
|
export * as listSearch from './listSearch';
|
|
@ -0,0 +1,289 @@
|
||||||
|
import type { IDataObject, ILoadOptionsFunctions, INodeListSearchResult } from 'n8n-workflow';
|
||||||
|
import { getSubfolders, microsoftApiRequest } from '../transport';
|
||||||
|
import { encodeOutlookId } from '../helpers/utils';
|
||||||
|
|
||||||
|
async function search(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
resource: string,
|
||||||
|
nameProperty: string,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
let response: IDataObject = {};
|
||||||
|
|
||||||
|
if (paginationToken) {
|
||||||
|
response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
paginationToken, // paginationToken contains the full URL
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const qs: IDataObject = {
|
||||||
|
$select: `id,${nameProperty}`,
|
||||||
|
$top: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
const filterValue = encodeURI(filter);
|
||||||
|
qs.$filter = `contains(${nameProperty}, '${filterValue}')`;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await microsoftApiRequest.call(this, 'GET', resource, undefined, qs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
results: (response.value as IDataObject[]).map((entry: IDataObject) => {
|
||||||
|
return {
|
||||||
|
name: entry[nameProperty] as string,
|
||||||
|
value: entry.id as string,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
paginationToken: response['@odata.nextLink'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchContacts(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
return search.call(this, '/contacts', 'displayName', filter, paginationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchCalendars(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
return search.call(this, '/calendars', 'name', filter, paginationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchDrafts(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
let response: IDataObject = {};
|
||||||
|
|
||||||
|
if (paginationToken) {
|
||||||
|
response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
paginationToken, // paginationToken contains the full URL
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const qs: IDataObject = {
|
||||||
|
$select: 'id,subject,bodyPreview,webLink',
|
||||||
|
$top: 100,
|
||||||
|
$filter: 'isDraft eq true',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
const filterValue = encodeURI(filter);
|
||||||
|
qs.$filter += ` AND contains(${'subject'}, '${filterValue}')`;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await microsoftApiRequest.call(this, 'GET', '/messages', undefined, qs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
results: (response.value as IDataObject[]).map((entry: IDataObject) => {
|
||||||
|
return {
|
||||||
|
name: (entry.subject || entry.bodyPreview) as string,
|
||||||
|
value: entry.id as string,
|
||||||
|
url: entry.webLink as string,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
paginationToken: response['@odata.nextLink'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchMessages(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
let response: IDataObject = {};
|
||||||
|
|
||||||
|
if (paginationToken) {
|
||||||
|
response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
paginationToken, // paginationToken contains the full URL
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const qs: IDataObject = {
|
||||||
|
$select: 'id,subject,bodyPreview,webLink',
|
||||||
|
$top: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
const filterValue = encodeURI(filter);
|
||||||
|
qs.$filter = `contains(${'subject'}, '${filterValue}')`;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await microsoftApiRequest.call(this, 'GET', '/messages', undefined, qs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
results: (response.value as IDataObject[]).map((entry: IDataObject) => {
|
||||||
|
return {
|
||||||
|
name: (entry.subject || entry.bodyPreview) as string,
|
||||||
|
value: entry.id as string,
|
||||||
|
url: entry.webLink as string,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
paginationToken: response['@odata.nextLink'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchEvents(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
let response: IDataObject = {};
|
||||||
|
|
||||||
|
const calendarId = this.getNodeParameter('calendarId', undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
if (paginationToken) {
|
||||||
|
response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
paginationToken, // paginationToken contains the full URL
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const qs: IDataObject = {
|
||||||
|
$select: 'id,subject,bodyPreview',
|
||||||
|
$top: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
const filterValue = encodeURI(filter);
|
||||||
|
qs.$filter = `contains(${'subject'}, '${filterValue}')`;
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/calendars/${calendarId}/events`,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
results: (response.value as IDataObject[]).map((entry: IDataObject) => {
|
||||||
|
return {
|
||||||
|
name: (entry.subject || entry.bodyPreview) as string,
|
||||||
|
value: entry.id as string,
|
||||||
|
url: `https://outlook.office365.com/calendar/item/${encodeOutlookId(entry.id as string)}`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
paginationToken: response['@odata.nextLink'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchFolders(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
filter?: string,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
let response: IDataObject = {};
|
||||||
|
|
||||||
|
if (paginationToken) {
|
||||||
|
response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
paginationToken, // paginationToken contains the full URL
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const qs: IDataObject = {
|
||||||
|
$top: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
response = await microsoftApiRequest.call(this, 'GET', '/mailFolders', undefined, qs);
|
||||||
|
}
|
||||||
|
|
||||||
|
let folders = await getSubfolders.call(this, response.value as IDataObject[]);
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
filter = filter.toLowerCase();
|
||||||
|
folders = folders.filter((folder) =>
|
||||||
|
((folder.displayName as string) || '').toLowerCase().includes(filter as string),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
results: folders.map((entry: IDataObject) => {
|
||||||
|
return {
|
||||||
|
name: entry.displayName as string,
|
||||||
|
value: entry.id as string,
|
||||||
|
url: `https://outlook.office365.com/mail/${encodeOutlookId(entry.id as string)}`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
paginationToken: response['@odata.nextLink'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function searchAttachments(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
paginationToken?: string,
|
||||||
|
): Promise<INodeListSearchResult> {
|
||||||
|
let response: IDataObject = {};
|
||||||
|
|
||||||
|
const messageId = this.getNodeParameter('messageId', undefined, {
|
||||||
|
extractValue: true,
|
||||||
|
}) as string;
|
||||||
|
|
||||||
|
if (paginationToken) {
|
||||||
|
response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
'',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
paginationToken, // paginationToken contains the full URL
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const qs: IDataObject = {
|
||||||
|
$select: 'id,name',
|
||||||
|
$top: 100,
|
||||||
|
};
|
||||||
|
|
||||||
|
response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${messageId}/attachments`,
|
||||||
|
undefined,
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
results: (response.value as IDataObject[]).map((entry: IDataObject) => {
|
||||||
|
return {
|
||||||
|
name: entry.name as string,
|
||||||
|
value: entry.id as string,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
paginationToken: response['@odata.nextLink'],
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
import type { ILoadOptionsFunctions, INodePropertyOptions } from 'n8n-workflow';
|
||||||
|
import { getSubfolders, microsoftApiRequestAllItems } from '../transport';
|
||||||
|
|
||||||
|
export async function getCategoriesNames(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const categories = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
'/outlook/masterCategories',
|
||||||
|
);
|
||||||
|
for (const category of categories) {
|
||||||
|
returnData.push({
|
||||||
|
name: category.displayName as string,
|
||||||
|
value: category.displayName as string,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getFolders(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const response = await microsoftApiRequestAllItems.call(this, 'value', 'GET', '/mailFolders', {});
|
||||||
|
const folders = await getSubfolders.call(this, response);
|
||||||
|
for (const folder of folders) {
|
||||||
|
returnData.push({
|
||||||
|
name: folder.displayName as string,
|
||||||
|
value: folder.id as string,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCalendarGroups(
|
||||||
|
this: ILoadOptionsFunctions,
|
||||||
|
): Promise<INodePropertyOptions[]> {
|
||||||
|
const returnData: INodePropertyOptions[] = [];
|
||||||
|
const calendars = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
'/calendarGroups',
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
for (const calendar of calendars) {
|
||||||
|
returnData.push({
|
||||||
|
name: calendar.name as string,
|
||||||
|
value: calendar.id as string,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
|
@ -0,0 +1,224 @@
|
||||||
|
import type { OptionsWithUri } from 'request';
|
||||||
|
|
||||||
|
import {
|
||||||
|
type IDataObject,
|
||||||
|
type IExecuteFunctions,
|
||||||
|
type IExecuteSingleFunctions,
|
||||||
|
type ILoadOptionsFunctions,
|
||||||
|
type INodeExecutionData,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { prepareApiError } from '../helpers/utils';
|
||||||
|
|
||||||
|
export async function microsoftApiRequest(
|
||||||
|
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
|
||||||
|
method: string,
|
||||||
|
resource: string,
|
||||||
|
body: IDataObject = {},
|
||||||
|
qs: IDataObject = {},
|
||||||
|
uri?: string,
|
||||||
|
headers: IDataObject = {},
|
||||||
|
option: IDataObject = { json: true },
|
||||||
|
) {
|
||||||
|
const credentials = await this.getCredentials('microsoftOutlookOAuth2Api');
|
||||||
|
|
||||||
|
let apiUrl = `https://graph.microsoft.com/v1.0/me${resource}`;
|
||||||
|
// If accessing shared mailbox
|
||||||
|
if (credentials.useShared && credentials.userPrincipalName) {
|
||||||
|
apiUrl = `https://graph.microsoft.com/v1.0/users/${credentials.userPrincipalName}${resource}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: OptionsWithUri = {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
method,
|
||||||
|
body,
|
||||||
|
qs,
|
||||||
|
uri: uri || apiUrl,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
Object.assign(options, option);
|
||||||
|
|
||||||
|
if (Object.keys(headers).length !== 0) {
|
||||||
|
options.headers = Object.assign({}, options.headers, headers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(body).length === 0) {
|
||||||
|
delete options.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.helpers.requestWithAuthentication.call(
|
||||||
|
this,
|
||||||
|
'microsoftOutlookOAuth2Api',
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (
|
||||||
|
((error.message || '').toLowerCase().includes('bad request') ||
|
||||||
|
(error.message || '').toLowerCase().includes('unknown error')) &&
|
||||||
|
error.description
|
||||||
|
) {
|
||||||
|
let updatedError;
|
||||||
|
// Try to return the error prettier, otherwise return the original one repalcing the message with the description
|
||||||
|
try {
|
||||||
|
updatedError = prepareApiError.call(this, error);
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
if (updatedError) throw updatedError;
|
||||||
|
|
||||||
|
error.message = error.description;
|
||||||
|
error.description = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function microsoftApiRequestAllItems(
|
||||||
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
|
propertyName: string,
|
||||||
|
method: string,
|
||||||
|
endpoint: string,
|
||||||
|
body: IDataObject = {},
|
||||||
|
query: IDataObject = {},
|
||||||
|
headers: IDataObject = {},
|
||||||
|
) {
|
||||||
|
const returnData: IDataObject[] = [];
|
||||||
|
|
||||||
|
let responseData;
|
||||||
|
let nextLink: string | undefined;
|
||||||
|
query.$top = 100;
|
||||||
|
|
||||||
|
do {
|
||||||
|
responseData = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
method,
|
||||||
|
endpoint,
|
||||||
|
body,
|
||||||
|
nextLink ? undefined : query, // Do not add query parameters as nextLink already contains them
|
||||||
|
nextLink,
|
||||||
|
headers,
|
||||||
|
);
|
||||||
|
nextLink = responseData['@odata.nextLink'];
|
||||||
|
returnData.push.apply(returnData, responseData[propertyName] as IDataObject[]);
|
||||||
|
} while (responseData['@odata.nextLink'] !== undefined);
|
||||||
|
|
||||||
|
return returnData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downloadAttachments(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
messages: IDataObject[] | IDataObject,
|
||||||
|
prefix: string,
|
||||||
|
) {
|
||||||
|
const elements: INodeExecutionData[] = [];
|
||||||
|
if (!Array.isArray(messages)) {
|
||||||
|
messages = [messages];
|
||||||
|
}
|
||||||
|
for (const message of messages) {
|
||||||
|
const element: INodeExecutionData = {
|
||||||
|
json: message,
|
||||||
|
binary: {},
|
||||||
|
};
|
||||||
|
if (message.hasAttachments === true) {
|
||||||
|
const attachments = await microsoftApiRequestAllItems.call(
|
||||||
|
this,
|
||||||
|
'value',
|
||||||
|
'GET',
|
||||||
|
`/messages/${message.id}/attachments`,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
for (const [index, attachment] of attachments.entries()) {
|
||||||
|
const response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${message.id}/attachments/${attachment.id}/$value`,
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
{ encoding: null, resolveWithFullResponse: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = Buffer.from(response.body as string, 'utf8');
|
||||||
|
element.binary![`${prefix}${index}`] = await this.helpers.prepareBinaryData(
|
||||||
|
data as unknown as Buffer,
|
||||||
|
attachment.name as string,
|
||||||
|
attachment.contentType as string,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Object.keys(element.binary!).length === 0) {
|
||||||
|
delete element.binary;
|
||||||
|
}
|
||||||
|
elements.push(element);
|
||||||
|
}
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMimeContent(
|
||||||
|
this: IExecuteFunctions,
|
||||||
|
messageId: string,
|
||||||
|
binaryPropertyName: string,
|
||||||
|
outputFileName?: string,
|
||||||
|
) {
|
||||||
|
const response = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/messages/${messageId}/$value`,
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
undefined,
|
||||||
|
{},
|
||||||
|
{ encoding: null, resolveWithFullResponse: true },
|
||||||
|
);
|
||||||
|
|
||||||
|
let mimeType: string | undefined;
|
||||||
|
if (response.headers['content-type']) {
|
||||||
|
mimeType = response.headers['content-type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = `${outputFileName || messageId}.eml`;
|
||||||
|
const data = Buffer.from(response.body as string, 'utf8');
|
||||||
|
const binary: IDataObject = {};
|
||||||
|
binary[binaryPropertyName] = await this.helpers.prepareBinaryData(
|
||||||
|
data as unknown as Buffer,
|
||||||
|
fileName,
|
||||||
|
mimeType,
|
||||||
|
);
|
||||||
|
|
||||||
|
return binary;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSubfolders(
|
||||||
|
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||||
|
folders: IDataObject[],
|
||||||
|
addPathToDisplayName = false,
|
||||||
|
) {
|
||||||
|
const returnData: IDataObject[] = [...folders];
|
||||||
|
for (const folder of folders) {
|
||||||
|
if ((folder.childFolderCount as number) > 0) {
|
||||||
|
let subfolders = await microsoftApiRequest.call(
|
||||||
|
this,
|
||||||
|
'GET',
|
||||||
|
`/mailFolders/${folder.id}/childFolders`,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (addPathToDisplayName) {
|
||||||
|
subfolders = subfolders.value.map((subfolder: IDataObject) => {
|
||||||
|
return {
|
||||||
|
...subfolder,
|
||||||
|
displayName: `${folder.displayName}/${subfolder.displayName}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
subfolders = subfolders.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
returnData.push(
|
||||||
|
...(await getSubfolders.call(this, subfolders as IDataObject[], addPathToDisplayName)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnData;
|
||||||
|
}
|
Loading…
Reference in a new issue