feat(Microsoft Outlook Node): Add support for recurring event instances (#29802)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Dawid Myslak 2026-05-07 13:45:52 +02:00 committed by GitHub
parent 8573197aef
commit dab3653f80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 367 additions and 9 deletions

View File

@ -0,0 +1,28 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
describe('Test MicrosoftOutlookV2, event => getAll', () => {
nock('https://graph.microsoft.com/v1.0/me')
.get(
'/calendars/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAAAJ9-JDAAA=/events',
)
.query(true)
.reply(200, {
value: [
{
id: 'AAMkADlhOTA0MTc5event1',
subject: 'Single Event',
bodyPreview: 'A one-time event',
start: { dateTime: '2023-09-05T07:00:00.0000000', timeZone: 'UTC' },
end: { dateTime: '2023-09-05T08:00:00.0000000', timeZone: 'UTC' },
organizer: { emailAddress: { name: 'Test User', address: 'test@example.com' } },
attendees: [],
webLink: 'https://outlook.office365.com/owa/?itemid=event1',
},
],
});
new NodeTestHarness().setupTests({
workflowFiles: ['getAll.workflow.json'],
});
});

View File

@ -0,0 +1,92 @@
{
"name": "Test getAll events",
"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": "getAll",
"fromAllCalendars": false,
"calendarId": {
"__rl": true,
"value": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAAAJ9-JDAAA=",
"mode": "list",
"cachedResultName": "Calendar"
},
"includeRecurringInstances": false,
"returnAll": false,
"limit": 10,
"output": "simple",
"filters": {}
},
"id": "baff6798-0304-4255-bdb0-dd3f2659373b",
"name": "Get Many Events",
"type": "n8n-nodes-base.microsoftOutlook",
"typeVersion": 2,
"position": [1040, 360],
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "iXJCki7i5Vz0bdks",
"name": "Microsoft Outlook account 2"
}
}
}
],
"pinData": {
"Get Many Events": [
{
"json": {
"id": "AAMkADlhOTA0MTc5event1",
"subject": "Single Event",
"bodyPreview": "A one-time event",
"start": {
"dateTime": "2023-09-05T07:00:00.0000000",
"timeZone": "UTC"
},
"end": {
"dateTime": "2023-09-05T08:00:00.0000000",
"timeZone": "UTC"
},
"organizer": {
"emailAddress": {
"name": "Test User",
"address": "test@example.com"
}
},
"attendees": [],
"webLink": "https://outlook.office365.com/owa/?itemid=event1"
}
}
]
},
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Get Many Events",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "dceb08aa-5897-44d1-afbc-748d1e8a2627",
"id": "2CYHzBXQw1nfPGtC",
"meta": {
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
},
"tags": []
}

View File

@ -0,0 +1,48 @@
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
import nock from 'nock';
describe('Test MicrosoftOutlookV2, event => getAll (calendarView)', () => {
nock('https://graph.microsoft.com/v1.0/me')
.get(
'/calendars/AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAAAJ9-JDAAA=/calendarView',
)
.query(true)
.reply(200, {
value: [
{
id: 'AAMkADlhOTA0MTc5event1',
subject: 'Single Event',
bodyPreview: 'A one-time event',
start: { dateTime: '2023-09-05T07:00:00.0000000', timeZone: 'UTC' },
end: { dateTime: '2023-09-05T08:00:00.0000000', timeZone: 'UTC' },
organizer: { emailAddress: { name: 'Test User', address: 'test@example.com' } },
attendees: [],
webLink: 'https://outlook.office365.com/owa/?itemid=event1',
},
{
id: 'AAMkADlhOTA0MTc5recurring1',
subject: 'Weekly Standup',
bodyPreview: 'Recurring weekly meeting - instance 1',
start: { dateTime: '2023-09-04T09:00:00.0000000', timeZone: 'UTC' },
end: { dateTime: '2023-09-04T09:30:00.0000000', timeZone: 'UTC' },
organizer: { emailAddress: { name: 'Test User', address: 'test@example.com' } },
attendees: [],
webLink: 'https://outlook.office365.com/owa/?itemid=recurring1',
},
{
id: 'AAMkADlhOTA0MTc5recurring2',
subject: 'Weekly Standup',
bodyPreview: 'Recurring weekly meeting - instance 2',
start: { dateTime: '2023-09-11T09:00:00.0000000', timeZone: 'UTC' },
end: { dateTime: '2023-09-11T09:30:00.0000000', timeZone: 'UTC' },
organizer: { emailAddress: { name: 'Test User', address: 'test@example.com' } },
attendees: [],
webLink: 'https://outlook.office365.com/owa/?itemid=recurring2',
},
],
});
new NodeTestHarness().setupTests({
workflowFiles: ['getAllCalendarView.workflow.json'],
});
});

View File

@ -0,0 +1,139 @@
{
"name": "Test getAll events with calendar view",
"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": "getAll",
"fromAllCalendars": false,
"calendarId": {
"__rl": true,
"value": "AAMkADlhOTA0MTc5LWUwOTMtNDRkZS05NzE0LTNlYmI0ZWM5OWI5OABGAAAAAABPLqzvT6b9RLP0CKzHiJrRBwBZf4De-LkrSqpPI8eyjUmAAAAAAAEGAABZf4De-LkrSqpPI8eyjUmAAAAJ9-JDAAA=",
"mode": "list",
"cachedResultName": "Calendar"
},
"includeRecurringInstances": true,
"startDateTime": "2023-09-01T00:00:00.000Z",
"endDateTime": "2023-09-30T23:59:59.000Z",
"returnAll": false,
"limit": 10,
"output": "simple"
},
"id": "calendar-view-node",
"name": "Get Many Events Calendar View",
"type": "n8n-nodes-base.microsoftOutlook",
"typeVersion": 2,
"position": [1040, 360],
"credentials": {
"microsoftOutlookOAuth2Api": {
"id": "iXJCki7i5Vz0bdks",
"name": "Microsoft Outlook account 2"
}
}
}
],
"pinData": {
"Get Many Events Calendar View": [
{
"json": {
"id": "AAMkADlhOTA0MTc5event1",
"subject": "Single Event",
"bodyPreview": "A one-time event",
"start": {
"dateTime": "2023-09-05T07:00:00.0000000",
"timeZone": "UTC"
},
"end": {
"dateTime": "2023-09-05T08:00:00.0000000",
"timeZone": "UTC"
},
"organizer": {
"emailAddress": {
"name": "Test User",
"address": "test@example.com"
}
},
"attendees": [],
"webLink": "https://outlook.office365.com/owa/?itemid=event1"
}
},
{
"json": {
"id": "AAMkADlhOTA0MTc5recurring1",
"subject": "Weekly Standup",
"bodyPreview": "Recurring weekly meeting - instance 1",
"start": {
"dateTime": "2023-09-04T09:00:00.0000000",
"timeZone": "UTC"
},
"end": {
"dateTime": "2023-09-04T09:30:00.0000000",
"timeZone": "UTC"
},
"organizer": {
"emailAddress": {
"name": "Test User",
"address": "test@example.com"
}
},
"attendees": [],
"webLink": "https://outlook.office365.com/owa/?itemid=recurring1"
}
},
{
"json": {
"id": "AAMkADlhOTA0MTc5recurring2",
"subject": "Weekly Standup",
"bodyPreview": "Recurring weekly meeting - instance 2",
"start": {
"dateTime": "2023-09-11T09:00:00.0000000",
"timeZone": "UTC"
},
"end": {
"dateTime": "2023-09-11T09:30:00.0000000",
"timeZone": "UTC"
},
"organizer": {
"emailAddress": {
"name": "Test User",
"address": "test@example.com"
}
},
"attendees": [],
"webLink": "https://outlook.office365.com/owa/?itemid=recurring2"
}
}
]
},
"connections": {
"When clicking \"Execute Workflow\"": {
"main": [
[
{
"node": "Get Many Events Calendar View",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "dceb08aa-5897-44d1-afbc-748d1e8a2628",
"id": "3CYHzBXQw1nfPGtD",
"meta": {
"instanceId": "b888bd11cd1ddbb95450babf3e199556799d999b896f650de768b8370ee50363"
},
"tags": []
}

View File

@ -21,6 +21,40 @@ export const properties: INodeProperties[] = [
},
},
},
{
displayName: 'Include Recurring Event Instances',
name: 'includeRecurringInstances',
type: 'boolean',
default: false,
description:
'Whether to expand recurring events into individual instances within the specified date range. When disabled, recurring events are returned as a single series master event.',
},
{
displayName: 'Start Date',
name: 'startDateTime',
type: 'dateTime',
default: '',
required: true,
description: 'Start of the date range to retrieve event instances for',
displayOptions: {
show: {
includeRecurringInstances: [true],
},
},
},
{
displayName: 'End Date',
name: 'endDateTime',
type: 'dateTime',
default: '',
required: true,
description: 'End of the date range to retrieve event instances for',
displayOptions: {
show: {
includeRecurringInstances: [true],
},
},
},
...returnAllOrLimit,
{
displayName: 'Output',
@ -61,6 +95,11 @@ export const properties: INodeProperties[] = [
type: 'collection',
placeholder: 'Add Filter',
default: {},
displayOptions: {
show: {
includeRecurringInstances: [false],
},
},
options: [
{
displayName: 'Filter Query',
@ -88,8 +127,11 @@ export async function execute(this: IExecuteFunctions, index: number) {
const qs = {} as IDataObject;
const returnAll = this.getNodeParameter('returnAll', index);
const filters = this.getNodeParameter('filters', index, {});
const output = this.getNodeParameter('output', index) as string;
const includeRecurringInstances = this.getNodeParameter(
'includeRecurringInstances',
index,
) as boolean;
if (output === 'fields') {
const fields = this.getNodeParameter('fields', index) as string[];
@ -100,6 +142,12 @@ export async function execute(this: IExecuteFunctions, index: number) {
qs.$select = 'id,subject,bodyPreview,start,end,organizer,attendees,webLink';
}
if (includeRecurringInstances) {
qs.startDateTime = this.getNodeParameter('startDateTime', index) as string;
qs.endDateTime = this.getNodeParameter('endDateTime', index) as string;
} else {
const filters = this.getNodeParameter('filters', index, {});
if (Object.keys(filters).length) {
const filterString: string[] = [];
@ -111,6 +159,7 @@ export async function execute(this: IExecuteFunctions, index: number) {
qs.$filter = filterString.join(' and ');
}
}
}
const calendars: string[] = [];
@ -133,7 +182,9 @@ export async function execute(this: IExecuteFunctions, index: number) {
const limit = this.getNodeParameter('limit', index, 0);
for (const calendarId of calendars) {
const endpoint = `/calendars/${calendarId}/events`;
const endpoint = includeRecurringInstances
? `/calendars/${calendarId}/calendarView`
: `/calendars/${calendarId}/events`;
if (returnAll) {
const response = await microsoftApiRequestAllItems.call(