Skip to content

Commit

Permalink
feat(Google Calendar Node): Use resource locator component for calend…
Browse files Browse the repository at this point in the history
…ar parameters (#4410)

* use calendar RLC for event resource
* use calendar RLC for calendar resource
* listSearch getCalendars support query filter
* improve RLC parameter descriptions to match standards
* stricter google calendar id email regex with optional trailing whitespace
* calendarId RLC for Google Calendar Trigger node
* Event -> Get : Timezone RLC option
* Event -> Get Many : Timezone RLC option
* Calendar -> Availability : Timezone RLC option
* Removed unused loadOptions getTimezones; Removed unused imports
* fix prettier linting errors
  • Loading branch information
maspio authored Nov 29, 2022
1 parent 47b9d22 commit b319671
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 116 deletions.
89 changes: 73 additions & 16 deletions packages/nodes-base/nodes/Google/Calendar/CalendarDescription.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { INodeProperties } from 'n8n-workflow';
import { TIMEZONE_VALIDATION_REGEX } from './GenericFunctions';

export const calendarOperations: INodeProperties[] = [
{
Expand Down Expand Up @@ -28,21 +29,50 @@ export const calendarFields: INodeProperties[] = [
/* calendar:availability */
/* -------------------------------------------------------------------------- */
{
displayName: 'Calendar Name or ID',
displayName: 'Calendar',
name: 'calendar',
type: 'options',
description:
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
typeOptions: {
loadOptionsMethod: 'getCalendars',
},
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',
},
],
displayOptions: {
show: {
resource: ['calendar'],
},
},
default: '',
},
{
displayName: 'Start Time',
Expand Down Expand Up @@ -110,15 +140,42 @@ export const calendarFields: INodeProperties[] = [
description: 'The format to return the data in',
},
{
displayName: 'Timezone Name or ID',
displayName: 'Timezone',
name: 'timezone',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getTimezones',
},
default: '',
description:
'Time zone used in the response. By default n8n timezone is used. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
description: 'Time zone used in the response. By default n8n timezone is used.',
modes: [
{
displayName: 'Timezone',
name: 'list',
type: 'list',
placeholder: 'Select a Timezone...',
typeOptions: {
searchListMethod: 'getTimezones',
searchable: true,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
validation: [
{
type: 'regex',
properties: {
regex: TIMEZONE_VALIDATION_REGEX,
errorMessage: 'Not a valid Timezone',
},
},
],
extractValue: {
type: 'regex',
regex: '([-+/_a-zA-Z0-9]*)',
},
placeholder: 'Europe/Berlin',
},
],
},
],
},
Expand Down
131 changes: 109 additions & 22 deletions packages/nodes-base/nodes/Google/Calendar/EventDescription.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { INodeProperties } from 'n8n-workflow';

import { TIMEZONE_VALIDATION_REGEX } from './GenericFunctions';

export const eventOperations: INodeProperties[] = [
{
displayName: 'Operation',
Expand Down Expand Up @@ -52,21 +54,50 @@ export const eventFields: INodeProperties[] = [
/* event:getAll */
/* -------------------------------------------------------------------------- */
{
displayName: 'Calendar Name or ID',
displayName: 'Calendar',
name: 'calendar',
type: 'options',
description:
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
typeOptions: {
loadOptionsMethod: 'getCalendars',
},
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',
},
],
displayOptions: {
show: {
resource: ['event'],
},
},
default: '',
},

/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -526,15 +557,43 @@ export const eventFields: INodeProperties[] = [
'The maximum number of attendees to include in the response. If there are more than the specified number of attendees, only the participant is returned.',
},
{
displayName: 'Timezone Name or ID',
displayName: 'Timezone',
name: 'timeZone',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getTimezones',
},
default: '',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
description:
'Time zone used in the response. The default is the time zone of the calendar. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
'Time zone used in the response. The default is the time zone of the calendar.',
modes: [
{
displayName: 'Timezone',
name: 'list',
type: 'list',
placeholder: 'Select a Timezone...',
typeOptions: {
searchListMethod: 'getTimezones',
searchable: true,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
validation: [
{
type: 'regex',
properties: {
regex: TIMEZONE_VALIDATION_REGEX,
errorMessage: 'Not a valid Timezone',
},
},
],
extractValue: {
type: 'regex',
regex: '([-+/_a-zA-Z0-9]*)',
},
placeholder: 'Europe/Berlin',
},
],
},
],
},
Expand Down Expand Up @@ -667,15 +726,43 @@ export const eventFields: INodeProperties[] = [
description: "Lower bound (exclusive) for an event's end time to filter by",
},
{
displayName: 'Timezone Name or ID',
displayName: 'Timezone',
name: 'timeZone',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getTimezones',
},
default: '',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
description:
'Time zone used in the response. The default is the time zone of the calendar. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
'Time zone used in the response. The default is the time zone of the calendar.',
modes: [
{
displayName: 'Timezone',
name: 'list',
type: 'list',
placeholder: 'Select a Timezone...',
typeOptions: {
searchListMethod: 'getTimezones',
searchable: true,
},
},
{
displayName: 'ID',
name: 'id',
type: 'string',
validation: [
{
type: 'regex',
properties: {
regex: TIMEZONE_VALIDATION_REGEX,
errorMessage: 'Not a valid Timezone',
},
},
],
extractValue: {
type: 'regex',
regex: '([-+/_a-zA-Z0-9]*)',
},
placeholder: 'Europe/Berlin',
},
],
},
{
displayName: 'Updated Min',
Expand Down
69 changes: 68 additions & 1 deletion packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@ import { OptionsWithUri } from 'request';

import { IExecuteFunctions, IExecuteSingleFunctions, ILoadOptionsFunctions } from 'n8n-core';

import { IDataObject, IPollFunctions, NodeApiError } from 'n8n-workflow';
import {
IDataObject,
INodeListSearchItems,
INodeListSearchResult,
IPollFunctions,
NodeApiError,
} from 'n8n-workflow';

import moment from 'moment-timezone';

export async function googleApiRequest(
this: IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions | IPollFunctions,
Expand Down Expand Up @@ -62,3 +70,62 @@ export async function googleApiRequestAllItems(

return returnData;
}

export function encodeURIComponentOnce(uri: string) {
// load options used to save encoded uri strings
return encodeURIComponent(decodeURIComponent(uri));
}

export async function getCalendars(
this: ILoadOptionsFunctions,
filter?: string,
): Promise<INodeListSearchResult> {
const calendars = (await googleApiRequestAllItems.call(
this,
'items',
'GET',
'/calendar/v3/users/me/calendarList',
)) as Array<{ id: string; summary: string }>;

const results: INodeListSearchItems[] = calendars
.map((c) => ({
name: c.summary,
value: c.id,
}))
.filter(
(c) =>
!filter ||
c.name.toLowerCase().includes(filter.toLowerCase()) ||
c.value?.toString() === filter,
)
.sort((a, b) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) return -1;
if (a.name.toLowerCase() > b.name.toLowerCase()) return 1;
return 0;
});
return { results };
}

export const TIMEZONE_VALIDATION_REGEX = `(${moment.tz
.names()
.map((t) => t.replace('+', '\\+'))
.join('|')})[ \t]*`;

export async function getTimezones(
this: ILoadOptionsFunctions,
filter?: string,
): Promise<INodeListSearchResult> {
const results: INodeListSearchItems[] = moment.tz
.names()
.map((timezone) => ({
name: timezone,
value: timezone,
}))
.filter(
(c) =>
!filter ||
c.name.toLowerCase().includes(filter.toLowerCase()) ||
c.value?.toString() === filter,
);
return { results };
}
Loading

0 comments on commit b319671

Please sign in to comment.