n8n/packages/nodes-base/nodes/Google/Calendar/GoogleCalendarTrigger.node.ts
Jon 9d241a0d6d
feat(Google Calendar Trigger Node): Add support for cancelled events (#7436)
Github issue / Community forum post (link here to close automatically):
https://community.n8n.io/t/google-calendar-trigger-deleted-events/10612

This PR adds support for triggering workflows when Google Calendar
events are cancelled.
2023-10-16 14:09:23 +01:00

249 lines
6.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type {
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeDescription,
IPollFunctions,
} from 'n8n-workflow';
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
import moment from 'moment';
import { getCalendars, googleApiRequest, googleApiRequestAllItems } from './GenericFunctions';
export class GoogleCalendarTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Google Calendar Trigger',
name: 'googleCalendarTrigger',
icon: 'file:googleCalendar.svg',
group: ['trigger'],
version: 1,
subtitle: '={{$parameter["triggerOn"]}}',
description: 'Starts the workflow when Google Calendar events occur',
defaults: {
name: 'Google Calendar Trigger',
},
inputs: [],
outputs: ['main'],
credentials: [
{
name: 'googleCalendarOAuth2Api',
required: true,
},
],
polling: true,
properties: [
{
displayName: 'Calendar',
name: 'calendarId',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
required: true,
description: 'Google Calendar to operate on',
modes: [
{
displayName: 'Calendar',
name: 'list',
type: 'list',
placeholder: 'Select a Calendar...',
typeOptions: {
searchListMethod: 'getCalendars',
searchable: true,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
validation: [
{
type: 'regex',
properties: {
// calendar ids are emails. W3C email regex with optional trailing whitespace.
regex:
'(^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*(?:[ \t]+)*$)',
errorMessage: 'Not a valid Google Calendar ID',
},
},
],
extractValue: {
type: 'regex',
regex: '(^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)',
},
placeholder: 'name@google.com',
},
],
},
{
displayName: 'Trigger On',
name: 'triggerOn',
type: 'options',
required: true,
default: '',
options: [
{
name: 'Event Cancelled',
value: 'eventCancelled',
},
{
name: 'Event Created',
value: 'eventCreated',
},
{
name: 'Event Ended',
value: 'eventEnded',
},
{
name: 'Event Started',
value: 'eventStarted',
},
{
name: 'Event Updated',
value: 'eventUpdated',
},
],
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Match Term',
name: 'matchTerm',
type: 'string',
default: '',
description:
'Free text search terms to filter events that match these terms in any field, except for extended properties',
},
],
},
],
};
methods = {
listSearch: {
getCalendars,
},
};
async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
const poolTimes = this.getNodeParameter('pollTimes.item', []) as IDataObject[];
const triggerOn = this.getNodeParameter('triggerOn', '') as string;
const calendarId = this.getNodeParameter('calendarId', '', { extractValue: true }) as string;
const webhookData = this.getWorkflowStaticData('node');
const matchTerm = this.getNodeParameter('options.matchTerm', '') as string;
if (poolTimes.length === 0) {
throw new NodeOperationError(this.getNode(), 'Please set a poll time');
}
if (triggerOn === '') {
throw new NodeOperationError(this.getNode(), 'Please select an event');
}
if (calendarId === '') {
throw new NodeOperationError(this.getNode(), 'Please select a calendar');
}
const now = moment().utc().format();
const startDate = (webhookData.lastTimeChecked as string) || now;
const endDate = now;
const qs: IDataObject = {
showDeleted: false,
};
if (matchTerm !== '') {
qs.q = matchTerm;
}
let events;
if (
triggerOn === 'eventCreated' ||
triggerOn === 'eventUpdated' ||
triggerOn === 'eventCancelled'
) {
Object.assign(qs, {
updatedMin: startDate,
orderBy: 'updated',
showDeleted: triggerOn === 'eventCancelled',
});
} else if (triggerOn === 'eventStarted' || triggerOn === 'eventEnded') {
Object.assign(qs, {
singleEvents: true,
timeMin: moment(startDate).startOf('second').utc().format(),
timeMax: moment(endDate).endOf('second').utc().format(),
orderBy: 'startTime',
});
}
if (this.getMode() === 'manual') {
delete qs.updatedMin;
delete qs.timeMin;
delete qs.timeMax;
qs.maxResults = 1;
events = await googleApiRequest.call(
this,
'GET',
`/calendar/v3/calendars/${calendarId}/events`,
{},
qs,
);
events = events.items;
} else {
events = await googleApiRequestAllItems.call(
this,
'items',
'GET',
`/calendar/v3/calendars/${calendarId}/events`,
{},
qs,
);
if (triggerOn === 'eventCreated') {
events = events.filter((event: { created: string }) =>
moment(event.created).isBetween(startDate, endDate),
);
} else if (triggerOn === 'eventUpdated' || triggerOn === 'eventCancelled') {
events = events.filter(
(event: { created: string; updated: string }) =>
!moment(moment(event.created).format('YYYY-MM-DDTHH:mm:ss')).isSame(
moment(event.updated).format('YYYY-MM-DDTHH:mm:ss'),
),
);
if (triggerOn === 'eventCancelled') {
events = events.filter((event: { status: string }) => event.status === 'cancelled');
}
} else if (triggerOn === 'eventStarted') {
events = events.filter((event: { start: { dateTime: string } }) =>
moment(event.start.dateTime).isBetween(startDate, endDate, null, '[]'),
);
} else if (triggerOn === 'eventEnded') {
events = events.filter((event: { end: { dateTime: string } }) =>
moment(event.end.dateTime).isBetween(startDate, endDate, null, '[]'),
);
}
}
webhookData.lastTimeChecked = endDate;
if (Array.isArray(events) && events.length) {
return [this.helpers.returnJsonArray(events)];
}
if (this.getMode() === 'manual') {
throw new NodeApiError(this.getNode(), {
message: 'No data with the current filter could be found',
});
}
return null;
}
}