diff --git a/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts b/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts index 826e00f678..6892c88152 100644 --- a/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts +++ b/packages/nodes-base/nodes/Google/Calendar/EventDescription.ts @@ -839,6 +839,58 @@ export const eventFields: INodeProperties[] = [ default: 'no', description: 'Whether the event is all day or not', }, + { + displayName: 'Attendees', + name: 'attendeesUi', + type: 'fixedCollection', + placeholder: 'Add Attendees', + default: { + values: { + mode: 'add', + attendees: [], + }, + }, + options: [ + { + displayName: 'Values', + name: 'values', + values: [ + { + displayName: 'Mode', + name: 'mode', + type: 'options', + default: 'add', + options: [ + { + name: 'Add Attendees Below [Default]', + value: 'add', + }, + { + name: 'Replace Attendees with Those Below', + value: 'replace', + }, + ], + }, + { + displayName: 'Attendees', + name: 'attendees', + type: 'string', + typeOptions: { + multipleValues: true, + multipleValueButtonText: 'Add Attendee', + }, + default: '', + description: 'The attendees of the event. Multiple ones can be separated by comma.', + }, + ], + }, + ], + displayOptions: { + show: { + '@version': [{ _cnd: { gte: 1.2 } }], + }, + }, + }, { displayName: 'Attendees', name: 'attendees', @@ -849,6 +901,11 @@ export const eventFields: INodeProperties[] = [ }, default: '', description: 'The attendees of the event. Multiple ones can be separated by comma.', + displayOptions: { + show: { + '@version': [1, 1.1], + }, + }, }, { displayName: 'Color Name or ID', diff --git a/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts b/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts index 6d7d92087b..93866afaf9 100644 --- a/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts +++ b/packages/nodes-base/nodes/Google/Calendar/GoogleCalendar.node.ts @@ -34,7 +34,7 @@ export class GoogleCalendar implements INodeType { name: 'googleCalendar', icon: 'file:googleCalendar.svg', group: ['input'], - version: [1, 1.1], + version: [1, 1.1, 1.2], subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', description: 'Consume Google Calendar API', defaults: { @@ -502,6 +502,7 @@ export class GoogleCalendar implements INodeType { timeZone: updateTimezone, }; } + // nodeVersion < 1.2 if (updateFields.attendees) { body.attendees = []; (updateFields.attendees as string[]).forEach((attendee) => { @@ -514,6 +515,37 @@ export class GoogleCalendar implements INodeType { ); }); } + // nodeVersion >= 1.2 + if (updateFields.attendeesUi) { + const { mode, attendees } = ( + updateFields.attendeesUi as { + values: { + mode: string; + attendees: string[]; + }; + } + ).values; + body.attendees = []; + if (mode === 'add') { + const event = await googleApiRequest.call( + this, + 'GET', + `/calendar/v3/calendars/${calendarId}/events/${eventId}`, + ); + ((event?.attendees as IDataObject[]) || []).forEach((attendee) => { + body.attendees?.push(attendee); + }); + } + (attendees as string[]).forEach((attendee) => { + body.attendees!.push.apply( + body.attendees, + attendee + .split(',') + .map((a) => a.trim()) + .map((email) => ({ email })), + ); + }); + } if (updateFields.color) { body.colorId = updateFields.color as string; } diff --git a/packages/nodes-base/nodes/Google/Calendar/test/node/event.update.test.ts b/packages/nodes-base/nodes/Google/Calendar/test/node/event.update.test.ts new file mode 100644 index 0000000000..39fa8a3b0a --- /dev/null +++ b/packages/nodes-base/nodes/Google/Calendar/test/node/event.update.test.ts @@ -0,0 +1,128 @@ +import type { MockProxy } from 'jest-mock-extended'; +import { mock } from 'jest-mock-extended'; +import type { INode, IExecuteFunctions } from 'n8n-workflow'; + +import { GoogleCalendar } from '../../GoogleCalendar.node'; + +import * as genericFunctions from '../../GenericFunctions'; + +jest.mock('../../GenericFunctions', () => ({ + getTimezones: jest.fn(), + googleApiRequest: jest.fn(), + googleApiRequestAllItems: jest.fn(), + addTimezoneToDate: jest.fn(), + addNextOccurrence: jest.fn(), + encodeURIComponentOnce: jest.fn(), +})); + +describe('RespondToWebhook Node', () => { + let googleCalendar: GoogleCalendar; + let mockExecuteFunctions: MockProxy; + + beforeEach(() => { + googleCalendar = new GoogleCalendar(); + mockExecuteFunctions = mock({ + getInputData: jest.fn(), + getNode: jest.fn(), + getNodeParameter: jest.fn(), + getTimezone: jest.fn(), + helpers: { + constructExecutionMetaData: jest.fn().mockReturnValue([]), + }, + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('Google Calendar > Event > Update', () => { + it('should update replace attendees in version 1.1', async () => { + mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('event'); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('update'); + mockExecuteFunctions.getTimezone.mockReturnValueOnce('Europe/Berlin'); + mockExecuteFunctions.getNode.mockReturnValue(mock({ typeVersion: 1.1 })); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('myCalendar'); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('myEvent'); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(true); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({ + attendees: ['email1@mail.com'], + }); + + await googleCalendar.execute.call(mockExecuteFunctions); + + expect(genericFunctions.googleApiRequest).toHaveBeenCalledWith( + 'PATCH', + '/calendar/v3/calendars/undefined/events/myEvent', + { attendees: [{ email: 'email1@mail.com' }] }, + {}, + ); + }); + + it('should update replace attendees', async () => { + mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('event'); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('update'); + mockExecuteFunctions.getTimezone.mockReturnValueOnce('Europe/Berlin'); + mockExecuteFunctions.getNode.mockReturnValue(mock({ typeVersion: 1.2 })); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('myCalendar'); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('myEvent'); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(true); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({ + attendeesUi: { + values: { + mode: 'replace', + attendees: ['email1@mail.com'], + }, + }, + }); + + await googleCalendar.execute.call(mockExecuteFunctions); + + expect(genericFunctions.googleApiRequest).toHaveBeenCalledWith( + 'PATCH', + '/calendar/v3/calendars/undefined/events/myEvent', + { attendees: [{ email: 'email1@mail.com' }] }, + {}, + ); + }); + + it('should update add attendees', async () => { + mockExecuteFunctions.getInputData.mockReturnValue([{ json: {} }]); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('event'); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('update'); + mockExecuteFunctions.getTimezone.mockReturnValueOnce('Europe/Berlin'); + mockExecuteFunctions.getNode.mockReturnValue(mock({ typeVersion: 1.2 })); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('myCalendar'); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce('myEvent'); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce(true); + mockExecuteFunctions.getNodeParameter.mockReturnValueOnce({ + attendeesUi: { + values: { + mode: 'add', + attendees: ['email1@mail.com'], + }, + }, + }); + (genericFunctions.googleApiRequest as jest.Mock).mockResolvedValueOnce({ + attendees: [{ email: 'email2@mail.com' }], + }); + + await googleCalendar.execute.call(mockExecuteFunctions); + + expect(genericFunctions.googleApiRequest).toHaveBeenCalledTimes(2); + + expect(genericFunctions.googleApiRequest).toHaveBeenCalledWith( + 'GET', + '/calendar/v3/calendars/undefined/events/myEvent', + ); + expect(genericFunctions.googleApiRequest).toHaveBeenCalledWith( + 'PATCH', + '/calendar/v3/calendars/undefined/events/myEvent', + { attendees: [{ email: 'email2@mail.com' }, { email: 'email1@mail.com' }] }, + {}, + ); + }); + }); +});