Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Google Calendar Node): Use resource locator component for calendar parameters #4410

Merged
merged 12 commits into from
Nov 29, 2022
Merged
88 changes: 72 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,49 @@ 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: '[email protected]',
},
],
displayOptions: {
show: {
resource: ['calendar'],
},
},
default: '',
},
{
displayName: 'Start Time',
Expand Down Expand Up @@ -110,15 +139,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
132 changes: 108 additions & 24 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,49 @@ 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: '[email protected]',
},
],
displayOptions: {
show: {
resource: ['event'],
},
},
default: '',
},

/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -526,15 +556,42 @@ 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: '',
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>.',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
description: '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 +724,42 @@ 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: '',
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>.',
type: 'resourceLocator',
default: { mode: 'list', value: '' },
description: '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
59 changes: 58 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,9 @@ 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 +64,58 @@ 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