Merge branch 'n8n-io:master' into Add-schema-registry-into-kafka

This commit is contained in:
Ricardo Georgel
2021-06-27 12:02:30 -03:00
committed by GitHub
537 changed files with 11390 additions and 2115 deletions

View File

@@ -0,0 +1,551 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeDescription,
NodeOperationError,
} from 'n8n-workflow';
import {
actionNetworkApiRequest,
adjustEventPayload,
adjustPersonPayload,
adjustPetitionPayload,
handleListing,
makeOsdiLink,
resourceLoaders,
simplifyResponse,
} from './GenericFunctions';
import {
attendanceFields,
attendanceOperations,
eventFields,
eventOperations,
personFields,
personOperations,
personTagFields,
personTagOperations,
petitionFields,
petitionOperations,
signatureFields,
signatureOperations,
tagFields,
tagOperations,
} from './descriptions';
import {
AllFieldsUi,
EmailAddressUi,
Operation,
PersonResponse,
Resource,
Response,
} from './types';
export class ActionNetwork implements INodeType {
description: INodeTypeDescription = {
displayName: 'Action Network',
name: 'actionNetwork',
icon: 'file:actionNetwork.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["resource"] + ": " + $parameter["operation"]}}',
description: 'Consume the Action Network API',
defaults: {
name: 'Action Network',
color: '#9dd3ed',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'actionNetworkApi',
required: true,
},
],
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'Attendance',
value: 'attendance',
},
{
name: 'Event',
value: 'event',
},
{
name: 'Person',
value: 'person',
},
{
name: 'Person Tag',
value: 'personTag',
},
{
name: 'Petition',
value: 'petition',
},
{
name: 'Signature',
value: 'signature',
},
{
name: 'Tag',
value: 'tag',
},
],
default: 'attendance',
description: 'Resource to consume',
},
...attendanceOperations,
...attendanceFields,
...eventOperations,
...eventFields,
...personOperations,
...personFields,
...petitionOperations,
...petitionFields,
...signatureOperations,
...signatureFields,
...tagOperations,
...tagFields,
...personTagOperations,
...personTagFields,
],
};
methods = {
loadOptions: resourceLoaders,
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const resource = this.getNodeParameter('resource', 0) as Resource;
const operation = this.getNodeParameter('operation', 0) as Operation;
let response;
for (let i = 0; i < items.length; i++) {
try {
if (resource === 'attendance') {
// **********************************************************************
// attendance
// **********************************************************************
// https://actionnetwork.org/docs/v2/attendances
if (operation === 'create') {
// ----------------------------------------
// attendance: create
// ----------------------------------------
const personId = this.getNodeParameter('personId', i) as string;
const eventId = this.getNodeParameter('eventId', i);
const body = makeOsdiLink(personId) as IDataObject;
const endpoint = `/events/${eventId}/attendances`;
response = await actionNetworkApiRequest.call(this, 'POST', endpoint, body);
} else if (operation === 'get') {
// ----------------------------------------
// attendance: get
// ----------------------------------------
const eventId = this.getNodeParameter('eventId', i);
const attendanceId = this.getNodeParameter('attendanceId', i);
const endpoint = `/events/${eventId}/attendances/${attendanceId}`;
response = await actionNetworkApiRequest.call(this, 'GET', endpoint);
} else if (operation === 'getAll') {
// ----------------------------------------
// attendance: getAll
// ----------------------------------------
const eventId = this.getNodeParameter('eventId', i);
const endpoint = `/events/${eventId}/attendances`;
response = await handleListing.call(this, 'GET', endpoint);
}
} else if (resource === 'event') {
// **********************************************************************
// event
// **********************************************************************
// https://actionnetwork.org/docs/v2/events
if (operation === 'create') {
// ----------------------------------------
// event: create
// ----------------------------------------
const body = {
origin_system: this.getNodeParameter('originSystem', i),
title: this.getNodeParameter('title', i),
} as IDataObject;
const additionalFields = this.getNodeParameter('additionalFields', i) as AllFieldsUi;
if (Object.keys(additionalFields).length) {
Object.assign(body, adjustEventPayload(additionalFields));
}
response = await actionNetworkApiRequest.call(this, 'POST', '/events', body);
} else if (operation === 'get') {
// ----------------------------------------
// event: get
// ----------------------------------------
const eventId = this.getNodeParameter('eventId', i);
response = await actionNetworkApiRequest.call(this, 'GET', `/events/${eventId}`);
} else if (operation === 'getAll') {
// ----------------------------------------
// event: getAll
// ----------------------------------------
response = await handleListing.call(this, 'GET', '/events');
}
} else if (resource === 'person') {
// **********************************************************************
// person
// **********************************************************************
// https://actionnetwork.org/docs/v2/people
if (operation === 'create') {
// ----------------------------------------
// person: create
// ----------------------------------------
const emailAddresses = this.getNodeParameter('email_addresses', i) as EmailAddressUi;
const body = {
person: {
email_addresses: [emailAddresses.email_addresses_fields], // only one accepted by API
},
} as IDataObject;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (Object.keys(additionalFields).length) {
Object.assign(body.person, adjustPersonPayload(additionalFields));
}
response = await actionNetworkApiRequest.call(this, 'POST', '/people', body);
} else if (operation === 'get') {
// ----------------------------------------
// person: get
// ----------------------------------------
const personId = this.getNodeParameter('personId', i);
response = await actionNetworkApiRequest.call(this, 'GET', `/people/${personId}`) as PersonResponse;
} else if (operation === 'getAll') {
// ----------------------------------------
// person: getAll
// ----------------------------------------
response = await handleListing.call(this, 'GET', '/people') as PersonResponse[];
} else if (operation === 'update') {
// ----------------------------------------
// person: update
// ----------------------------------------
const personId = this.getNodeParameter('personId', i);
const body = {} as IDataObject;
const updateFields = this.getNodeParameter('updateFields', i) as IDataObject;
if (Object.keys(updateFields).length) {
Object.assign(body, adjustPersonPayload(updateFields));
} else {
throw new NodeOperationError(
this.getNode(),
`Please enter at least one field to update for the ${resource}.`,
);
}
response = await actionNetworkApiRequest.call(this, 'PUT', `/people/${personId}`, body);
}
} else if (resource === 'petition') {
// **********************************************************************
// petition
// **********************************************************************
// https://actionnetwork.org/docs/v2/petitions
if (operation === 'create') {
// ----------------------------------------
// petition: create
// ----------------------------------------
const body = {
origin_system: this.getNodeParameter('originSystem', i),
title: this.getNodeParameter('title', i),
} as IDataObject;
const additionalFields = this.getNodeParameter('additionalFields', i) as AllFieldsUi;
if (Object.keys(additionalFields).length) {
Object.assign(body, adjustPetitionPayload(additionalFields));
}
response = await actionNetworkApiRequest.call(this, 'POST', '/petitions', body);
} else if (operation === 'get') {
// ----------------------------------------
// petition: get
// ----------------------------------------
const petitionId = this.getNodeParameter('petitionId', i);
const endpoint = `/petitions/${petitionId}`;
response = await actionNetworkApiRequest.call(this, 'GET', endpoint);
} else if (operation === 'getAll') {
// ----------------------------------------
// petition: getAll
// ----------------------------------------
response = await handleListing.call(this, 'GET', '/petitions');
} else if (operation === 'update') {
// ----------------------------------------
// petition: update
// ----------------------------------------
const petitionId = this.getNodeParameter('petitionId', i);
const body = {} as IDataObject;
const updateFields = this.getNodeParameter('updateFields', i) as AllFieldsUi;
if (Object.keys(updateFields).length) {
Object.assign(body, adjustPetitionPayload(updateFields));
} else {
throw new NodeOperationError(
this.getNode(),
`Please enter at least one field to update for the ${resource}.`,
);
}
response = await actionNetworkApiRequest.call(this, 'PUT', `/petitions/${petitionId}`, body);
}
} else if (resource === 'signature') {
// **********************************************************************
// signature
// **********************************************************************
// https://actionnetwork.org/docs/v2/signatures
if (operation === 'create') {
// ----------------------------------------
// signature: create
// ----------------------------------------
const personId = this.getNodeParameter('personId', i) as string;
const petitionId = this.getNodeParameter('petitionId', i);
const body = makeOsdiLink(personId) as IDataObject;
const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
if (Object.keys(additionalFields).length) {
Object.assign(body, additionalFields);
}
const endpoint = `/petitions/${petitionId}/signatures`;
response = await actionNetworkApiRequest.call(this, 'POST', endpoint, body);
} else if (operation === 'get') {
// ----------------------------------------
// signature: get
// ----------------------------------------
const petitionId = this.getNodeParameter('petitionId', i);
const signatureId = this.getNodeParameter('signatureId', i);
const endpoint = `/petitions/${petitionId}/signatures/${signatureId}`;
response = await actionNetworkApiRequest.call(this, 'GET', endpoint);
} else if (operation === 'getAll') {
// ----------------------------------------
// signature: getAll
// ----------------------------------------
const petitionId = this.getNodeParameter('petitionId', i);
const endpoint = `/petitions/${petitionId}/signatures`;
response = await handleListing.call(this, 'GET', endpoint);
} else if (operation === 'update') {
// ----------------------------------------
// signature: update
// ----------------------------------------
const petitionId = this.getNodeParameter('petitionId', i);
const signatureId = this.getNodeParameter('signatureId', i);
const body = {};
const updateFields = this.getNodeParameter('updateFields', i) as AllFieldsUi;
if (Object.keys(updateFields).length) {
Object.assign(body, updateFields);
} else {
throw new NodeOperationError(
this.getNode(),
`Please enter at least one field to update for the ${resource}.`,
);
}
const endpoint = `/petitions/${petitionId}/signatures/${signatureId}`;
response = await actionNetworkApiRequest.call(this, 'PUT', endpoint, body);
}
} else if (resource === 'tag') {
// **********************************************************************
// tag
// **********************************************************************
// https://actionnetwork.org/docs/v2/tags
if (operation === 'create') {
// ----------------------------------------
// tag: create
// ----------------------------------------
const body = {
name: this.getNodeParameter('name', i),
} as IDataObject;
response = await actionNetworkApiRequest.call(this, 'POST', '/tags', body);
} else if (operation === 'get') {
// ----------------------------------------
// tag: get
// ----------------------------------------
const tagId = this.getNodeParameter('tagId', i);
response = await actionNetworkApiRequest.call(this, 'GET', `/tags/${tagId}`);
} else if (operation === 'getAll') {
// ----------------------------------------
// tag: getAll
// ----------------------------------------
response = await handleListing.call(this, 'GET', '/tags');
}
} else if (resource === 'personTag') {
// **********************************************************************
// personTag
// **********************************************************************
// https://actionnetwork.org/docs/v2/taggings
if (operation === 'add') {
// ----------------------------------------
// personTag: add
// ----------------------------------------
const personId = this.getNodeParameter('personId', i) as string;
const tagId = this.getNodeParameter('tagId', i);
const body = makeOsdiLink(personId) as IDataObject;
const endpoint = `/tags/${tagId}/taggings`;
response = await actionNetworkApiRequest.call(this, 'POST', endpoint, body);
} else if (operation === 'remove') {
// ----------------------------------------
// personTag: remove
// ----------------------------------------
const tagId = this.getNodeParameter('tagId', i);
const taggingId = this.getNodeParameter('taggingId', i);
const endpoint = `/tags/${tagId}/taggings/${taggingId}`;
response = await actionNetworkApiRequest.call(this, 'DELETE', endpoint);
}
}
const simplify = this.getNodeParameter('simple', i, false) as boolean;
if (simplify) {
response = operation === 'getAll'
? response.map((i: Response) => simplifyResponse(i, resource))
: simplifyResponse(response, resource);
}
Array.isArray(response)
? returnData.push(...response)
: returnData.push(response);
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.message });
continue;
}
throw error;
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}

View File

@@ -0,0 +1,348 @@
import {
IExecuteFunctions,
} from 'n8n-core';
import {
IDataObject,
ILoadOptionsFunctions,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow';
import {
OptionsWithUri,
} from 'request';
import {
flow,
omit,
} from 'lodash';
import {
AllFieldsUi,
FieldWithPrimaryField,
LinksFieldContainer,
PersonResponse,
PetitionResponse,
Resource,
Response,
} from './types';
export async function actionNetworkApiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions,
method: string,
endpoint: string,
body: IDataObject = {},
qs: IDataObject = {},
) {
const credentials = this.getCredentials('actionNetworkApi') as { apiKey: string } | undefined;
if (credentials === undefined) {
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
}
const options: OptionsWithUri = {
headers: {
'OSDI-API-Token': credentials.apiKey,
},
method,
body,
qs,
uri: `https://actionnetwork.org/api/v2${endpoint}`,
json: true,
};
if (!Object.keys(body).length) {
delete options.body;
}
if (!Object.keys(qs).length) {
delete options.qs;
}
try {
return await this.helpers.request!(options);
} catch (error) {
throw new NodeApiError(this.getNode(), error);
}
}
export async function handleListing(
this: IExecuteFunctions | ILoadOptionsFunctions,
method: string,
endpoint: string,
body: IDataObject = {},
qs: IDataObject = {},
options?: { returnAll: true },
) {
const returnData: IDataObject[] = [];
let responseData;
qs.perPage = 25; // max
qs.page = 1;
const returnAll = options?.returnAll ?? this.getNodeParameter('returnAll', 0, false) as boolean;
const limit = this.getNodeParameter('limit', 0, 0) as number;
const itemsKey = toItemsKey(endpoint);
do {
responseData = await actionNetworkApiRequest.call(this, method, endpoint, body, qs);
const items = responseData._embedded[itemsKey];
returnData.push(...items);
if (!returnAll && returnData.length >= limit) {
return returnData.slice(0, limit);
}
qs.page = responseData.page as number;
} while (responseData.total_pages && qs.page < responseData.total_pages);
return returnData;
}
// ----------------------------------------
// helpers
// ----------------------------------------
/**
* Convert an endpoint to the key needed to access data in the response.
*/
const toItemsKey = (endpoint: string) => {
// handle two-resource endpoint
if (
endpoint.includes('/signatures') ||
endpoint.includes('/attendances') ||
endpoint.includes('/taggings')
) {
endpoint = endpoint.split('/').pop()!;
}
return `osdi:${endpoint.replace(/\//g, '')}`;
};
export const extractId = (response: LinksFieldContainer) => {
return response._links.self.href.split('/').pop() ?? 'No ID';
};
export const makeOsdiLink = (personId: string) => {
return {
_links: {
'osdi:person': {
href: `https://actionnetwork.org/api/v2/people/${personId}`,
},
},
};
};
export const isPrimary = (field: FieldWithPrimaryField) => field.primary;
// ----------------------------------------
// field adjusters
// ----------------------------------------
function adjustLanguagesSpoken(allFields: AllFieldsUi) {
if (!allFields.languages_spoken) return allFields;
return {
...omit(allFields, ['languages_spoken']),
languages_spoken: [allFields.languages_spoken],
};
}
function adjustPhoneNumbers(allFields: AllFieldsUi) {
if (!allFields.phone_numbers) return allFields;
return {
...omit(allFields, ['phone_numbers']),
phone_numbers: [
allFields.phone_numbers.phone_numbers_fields,
],
};
}
function adjustPostalAddresses(allFields: AllFieldsUi) {
if (!allFields.postal_addresses) return allFields;
if (allFields.postal_addresses.postal_addresses_fields.length) {
const adjusted = allFields.postal_addresses.postal_addresses_fields.map((field) => {
const copy: IDataObject = {
...omit(field, ['address_lines', 'location']),
};
if (field.address_lines) {
copy.address_lines = [field.address_lines];
}
if (field.location) {
copy.location = field.location.location_fields;
}
return copy;
});
return {
...omit(allFields, ['postal_addresses']),
postal_addresses: adjusted,
};
}
}
function adjustLocation(allFields: AllFieldsUi) {
if (!allFields.location) return allFields;
const locationFields = allFields.location.postal_addresses_fields;
const adjusted: IDataObject = {
...omit(locationFields, ['address_lines', 'location']),
};
if (locationFields.address_lines) {
adjusted.address_lines = [locationFields.address_lines];
}
if (locationFields.location) {
adjusted.location = locationFields.location.location_fields;
}
return {
...omit(allFields, ['location']),
location: adjusted,
};
}
function adjustTargets(allFields: AllFieldsUi) {
if (!allFields.target) return allFields;
const adjusted = allFields.target.split(',').map(value => ({ name: value }));
return {
...omit(allFields, ['target']),
target: adjusted,
};
}
// ----------------------------------------
// payload adjusters
// ----------------------------------------
export const adjustPersonPayload = flow(
adjustLanguagesSpoken,
adjustPhoneNumbers,
adjustPostalAddresses,
);
export const adjustPetitionPayload = adjustTargets;
export const adjustEventPayload = adjustLocation;
// ----------------------------------------
// resource loaders
// ----------------------------------------
async function loadResource(this: ILoadOptionsFunctions, resource: string) {
return await handleListing.call(this, 'GET', `/${resource}`, {}, {}, { returnAll: true });
}
export const resourceLoaders = {
async getTags(this: ILoadOptionsFunctions) {
const tags = await loadResource.call(this, 'tags') as Array<{ name: string } & LinksFieldContainer>;
return tags.map((tag) => ({ name: tag.name, value: extractId(tag) }));
},
async getTaggings(this: ILoadOptionsFunctions) {
const tagId = this.getNodeParameter('tagId', 0);
const endpoint = `/tags/${tagId}/taggings`;
// two-resource endpoint, so direct call
const taggings = await handleListing.call(
this, 'GET', endpoint, {}, {}, { returnAll: true },
) as LinksFieldContainer[];
return taggings.map((tagging) => {
const taggingId = extractId(tagging);
return {
name: taggingId,
value: taggingId,
};
});
},
};
// ----------------------------------------
// response simplifiers
// ----------------------------------------
export const simplifyResponse = (response: Response, resource: Resource) => {
if (resource === 'person') {
return simplifyPersonResponse(response as PersonResponse);
} else if (resource === 'petition') {
return simplifyPetitionResponse(response as PetitionResponse);
}
const fieldsToSimplify = [
'identifiers',
'_links',
'action_network:sponsor',
'reminders',
];
return {
id: extractId(response),
...omit(response, fieldsToSimplify),
};
};
const simplifyPetitionResponse = (response: PetitionResponse) => {
const fieldsToSimplify = [
'identifiers',
'_links',
'action_network:hidden',
'_embedded',
];
return {
id: extractId(response),
...omit(response, fieldsToSimplify),
creator: simplifyPersonResponse(response._embedded['osdi:creator']),
};
};
const simplifyPersonResponse = (response: PersonResponse) => {
const emailAddress = response.email_addresses.filter(isPrimary);
const phoneNumber = response.phone_numbers.filter(isPrimary);
const postalAddress = response.postal_addresses.filter(isPrimary);
const fieldsToSimplify = [
'identifiers',
'email_addresses',
'phone_numbers',
'postal_addresses',
'languages_spoken',
'_links',
];
return {
id: extractId(response),
...omit(response, fieldsToSimplify),
...{ email_address: emailAddress[0].address || '' },
...{ phone_number: phoneNumber[0].number || '' },
...{
postal_address: {
...postalAddress && omit(postalAddress[0], 'address_lines'),
address_lines: postalAddress[0].address_lines ?? '',
},
},
language_spoken: response.languages_spoken[0],
};
};

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 1 79.89 75.96"><defs><style>.cls-1{fill:#9dd3ed;}</style></defs><title>actionnetwork</title><circle class="cls-1" cx="40.42" cy="5.43" r="5.43"/><path class="cls-1" d="M26.32,15.92c9.94-2.11,15.06,5.25,16.52,11.47.92,3.93,4.28,3.87,6.58,2.73S56,28.48,53,21.23c-1.82-4.41-4.14-8.44-8.26-11.08A6.42,6.42,0,0,1,34.66,8.3a6.88,6.88,0,0,1-.44-1.14c-8.85.1-14.7,8-16.45,13.26a18.85,18.85,0,0,1,8.55-4.5"/><circle class="cls-1" cx="5.43" cy="29.87" r="5.43"/><path class="cls-1" d="M11.23,46.45c1-10.11,9.51-12.8,15.88-12.33,4,.29,5-2.93,4.57-5.47s.4-6.78-7.41-6.1C19.52,23,15,24,11.23,27.12a6.37,6.37,0,0,1,.34,4.67,6.62,6.62,0,0,1-.93,1.86,6.39,6.39,0,0,1-4.19,2.57,6.31,6.31,0,0,1-1.22.08C2.68,44.78,8.52,52.73,13,56a18.92,18.92,0,0,1-1.75-9.51"/><circle class="cls-1" cx="18.08" cy="70.13" r="5.43"/><path class="cls-1" d="M35.65,69.73c-9.33-4-9.25-13-6.83-18.91,1.52-3.74-1.25-5.64-3.79-6S18.7,42.31,16.94,50c-1.07,4.64-1.51,9.27.32,13.81a6.44,6.44,0,0,1,4.55,1.12,6.47,6.47,0,0,1,2.63,6.24,7.55,7.55,0,0,1-.3,1.18c7.27,5,16.64,2,21.09-1.3a18.8,18.8,0,0,1-9.58-1.27"/><circle class="cls-1" cx="60.58" cy="70.53" r="5.43"/><path class="cls-1" d="M65.83,53.76c-6.81,7.55-15.28,4.6-20.11.42-3-2.64-5.74-.62-6.93,1.66S34.42,61,41.1,65.16c4.06,2.5,8.3,4.4,13.19,4.11a6.43,6.43,0,0,1,2.51-4,6.76,6.76,0,0,1,1.86-.93,6.38,6.38,0,0,1,4.9.44,5.94,5.94,0,0,1,1,.66c7.1-5.28,7.17-15.14,5.52-20.4a18.91,18.91,0,0,1-4.27,8.67"/><circle class="cls-1" cx="74.46" cy="30.68" r="5.43"/><path class="cls-1" d="M60.13,20.51c5.08,8.81-.34,16-5.8,19.25C50.87,41.85,52,45,53.76,46.87s3.6,5.76,9.57.68c3.64-3.08,6.75-6.54,8-11.27A6.42,6.42,0,0,1,70,26.09a6.21,6.21,0,0,1,.94-.77c-2.83-8.39-12.19-11.49-17.69-11.55a18.94,18.94,0,0,1,6.92,6.74"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,185 @@
import {
INodeProperties,
} from 'n8n-workflow';
import {
makeSimpleField,
} from './SharedFields';
export const attendanceOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'attendance',
],
},
},
options: [
{
name: 'Create',
value: 'create',
},
{
name: 'Get',
value: 'get',
},
{
name: 'Get All',
value: 'getAll',
},
],
default: 'create',
description: 'Operation to perform',
},
] as INodeProperties[];
export const attendanceFields = [
// ----------------------------------------
// attendance: create
// ----------------------------------------
{
displayName: 'Person ID',
name: 'personId',
description: 'ID of the person to create an attendance for.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'attendance',
],
operation: [
'create',
],
},
},
},
{
displayName: 'Event ID',
name: 'eventId',
description: 'ID of the event to create an attendance for.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'attendance',
],
operation: [
'create',
],
},
},
},
makeSimpleField('attendance', 'create'),
// ----------------------------------------
// attendance: get
// ----------------------------------------
{
displayName: 'Event ID',
name: 'eventId',
description: 'ID of the event whose attendance to retrieve.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'attendance',
],
operation: [
'get',
],
},
},
},
{
displayName: 'Attendance ID',
name: 'attendanceId',
description: 'ID of the attendance to retrieve.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'attendance',
],
operation: [
'get',
],
},
},
},
makeSimpleField('attendance', 'get'),
// ----------------------------------------
// attendance: getAll
// ----------------------------------------
{
displayName: 'Event ID',
name: 'eventId',
description: 'ID of the event to create an attendance for.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'attendance',
],
operation: [
'getAll',
],
},
},
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
description: 'Return all results.',
displayOptions: {
show: {
resource: [
'attendance',
],
operation: [
'getAll',
],
},
},
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 50,
description: 'The number of results to return.',
typeOptions: {
minValue: 1,
},
displayOptions: {
show: {
resource: [
'attendance',
],
operation: [
'getAll',
],
returnAll: [
false,
],
},
},
},
makeSimpleField('attendance', 'getAll'),
] as INodeProperties[];

View File

@@ -0,0 +1,168 @@
import {
INodeProperties,
} from 'n8n-workflow';
import {
eventAdditionalFieldsOptions,
makeSimpleField,
} from './SharedFields';
export const eventOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'event',
],
},
},
options: [
{
name: 'Create',
value: 'create',
},
{
name: 'Get',
value: 'get',
},
{
name: 'Get All',
value: 'getAll',
},
],
default: 'create',
description: 'Operation to perform',
},
] as INodeProperties[];
export const eventFields = [
// ----------------------------------------
// event: create
// ----------------------------------------
{
displayName: 'Origin System',
name: 'originSystem',
description: 'Source where the event originated.',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'create',
],
},
},
},
{
displayName: 'Title',
name: 'title',
description: 'Title of the event to create.',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'create',
],
},
},
},
makeSimpleField('event', 'create'),
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'create',
],
},
},
options: eventAdditionalFieldsOptions,
},
// ----------------------------------------
// event: get
// ----------------------------------------
{
displayName: 'Event ID',
name: 'eventId',
description: 'ID of the event to retrieve.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'get',
],
},
},
},
makeSimpleField('event', 'get'),
// ----------------------------------------
// event: getAll
// ----------------------------------------
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
description: 'Return all results.',
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'getAll',
],
},
},
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 50,
description: 'The number of results to return.',
typeOptions: {
minValue: 1,
},
displayOptions: {
show: {
resource: [
'event',
],
operation: [
'getAll',
],
returnAll: [
false,
],
},
},
},
makeSimpleField('event', 'getAll'),
] as INodeProperties[];

View File

@@ -0,0 +1,250 @@
import {
INodeProperties,
} from 'n8n-workflow';
import {
makeSimpleField,
personAdditionalFieldsOptions,
} from './SharedFields';
export const personOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'person',
],
},
},
options: [
{
name: 'Create',
value: 'create',
},
{
name: 'Get',
value: 'get',
},
{
name: 'Get All',
value: 'getAll',
},
{
name: 'Update',
value: 'update',
},
],
default: 'create',
description: 'Operation to perform',
},
] as INodeProperties[];
export const personFields = [
// ----------------------------------------
// person: create
// ----------------------------------------
makeSimpleField('person', 'create'),
{
displayName: 'Email Address', // on create, only _one_ must be passed in
name: 'email_addresses',
type: 'fixedCollection',
default: {},
placeholder: 'Add Email Address Field',
description: 'Persons email addresses.',
displayOptions: {
show: {
resource: [
'person',
],
operation: [
'create',
],
},
},
options: [
{
displayName: 'Email Addresses Fields',
name: 'email_addresses_fields',
values: [
{
displayName: 'Address',
name: 'address',
type: 'string',
default: '',
description: 'Person\'s email address.',
},
{
displayName: 'Primary',
name: 'primary',
type: 'hidden',
default: true,
description: 'Whether this is the person\'s primary email address.',
},
{
displayName: 'Status',
name: 'status',
type: 'options',
default: 'subscribed',
description: 'Subscription status of this email address.',
options: [
{
name: 'Bouncing',
value: 'bouncing',
},
{
name: 'Previous Bounce',
value: 'previous bounce',
},
{
name: 'Previous Spam Complaint',
value: 'previous spam complaint',
},
{
name: 'Spam Complaint',
value: 'spam complaint',
},
{
name: 'Subscribed',
value: 'subscribed',
},
{
name: 'Unsubscribed',
value: 'unsubscribed',
},
],
},
],
},
],
},
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'person',
],
operation: [
'create',
],
},
},
options: personAdditionalFieldsOptions,
},
// ----------------------------------------
// person: get
// ----------------------------------------
{
displayName: 'Person ID',
name: 'personId',
description: 'ID of the person to retrieve.',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'person',
],
operation: [
'get',
],
},
},
},
makeSimpleField('person', 'get'),
// ----------------------------------------
// person: getAll
// ----------------------------------------
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
description: 'Return all results.',
displayOptions: {
show: {
resource: [
'person',
],
operation: [
'getAll',
],
},
},
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 50,
description: 'The number of results to return.',
typeOptions: {
minValue: 1,
},
displayOptions: {
show: {
resource: [
'person',
],
operation: [
'getAll',
],
returnAll: [
false,
],
},
},
},
makeSimpleField('person', 'getAll'),
// ----------------------------------------
// person: update
// ----------------------------------------
{
displayName: 'Person ID',
name: 'personId',
description: 'ID of the person to update.',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'person',
],
operation: [
'update',
],
},
},
},
makeSimpleField('person', 'update'),
{
displayName: 'Update Fields',
name: 'updateFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'person',
],
operation: [
'update',
],
},
},
options: personAdditionalFieldsOptions,
},
] as INodeProperties[];

View File

@@ -0,0 +1,122 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const personTagOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'personTag',
],
},
},
options: [
{
name: 'Add',
value: 'add',
},
{
name: 'Remove',
value: 'remove',
},
],
default: 'add',
description: 'Operation to perform',
},
] as INodeProperties[];
export const personTagFields = [
// ----------------------------------------
// personTag: add
// ----------------------------------------
{
displayName: 'Tag ID',
name: 'tagId',
description: 'ID of the tag to add.',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getTags',
},
required: true,
default: [],
displayOptions: {
show: {
resource: [
'personTag',
],
operation: [
'add',
],
},
},
},
{
displayName: 'Person ID',
name: 'personId',
description: 'ID of the person to add the tag to.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'personTag',
],
operation: [
'add',
],
},
},
},
// ----------------------------------------
// personTag: remove
// ----------------------------------------
{
displayName: 'Tag ID',
name: 'tagId',
description: 'ID of the tag whose tagging to delete.',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getTags',
},
default: [],
required: true,
displayOptions: {
show: {
resource: [
'personTag',
],
operation: [
'remove',
],
},
},
},
{
displayName: 'Tagging ID',
name: 'taggingId',
description: 'ID of the tagging to remove.',
type: 'options',
typeOptions: {
loadOptionsDependsOn: 'tagId',
loadOptionsMethod: 'getTaggings',
},
required: true,
default: [],
displayOptions: {
show: {
resource: [
'personTag',
],
operation: [
'remove',
],
},
},
},
] as INodeProperties[];

View File

@@ -0,0 +1,213 @@
import {
INodeProperties,
} from 'n8n-workflow';
import {
makeSimpleField,
petitionAdditionalFieldsOptions,
} from './SharedFields';
export const petitionOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'petition',
],
},
},
options: [
{
name: 'Create',
value: 'create',
},
{
name: 'Get',
value: 'get',
},
{
name: 'Get All',
value: 'getAll',
},
{
name: 'Update',
value: 'update',
},
],
default: 'create',
description: 'Operation to perform',
},
] as INodeProperties[];
export const petitionFields = [
// ----------------------------------------
// petition: create
// ----------------------------------------
{
displayName: 'Origin System',
name: 'originSystem',
description: 'Source where the petition originated.',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'petition',
],
operation: [
'create',
],
},
},
},
{
displayName: 'Title',
name: 'title',
description: 'Title of the petition to create.',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'petition',
],
operation: [
'create',
],
},
},
},
makeSimpleField('petition', 'create'),
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'petition',
],
operation: [
'create',
],
},
},
options: petitionAdditionalFieldsOptions,
},
// ----------------------------------------
// petition: get
// ----------------------------------------
{
displayName: 'Petition ID',
name: 'petitionId',
description: 'ID of the petition to retrieve.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'petition',
],
operation: [
'get',
],
},
},
},
makeSimpleField('petition', 'get'),
// ----------------------------------------
// petition: getAll
// ----------------------------------------
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
description: 'Return all results.',
displayOptions: {
show: {
resource: [
'petition',
],
operation: [
'getAll',
],
},
},
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 50,
description: 'The number of results to return.',
typeOptions: {
minValue: 1,
},
displayOptions: {
show: {
resource: [
'petition',
],
operation: [
'getAll',
],
returnAll: [
false,
],
},
},
},
makeSimpleField('petition', 'getAll'),
// ----------------------------------------
// petition: update
// ----------------------------------------
{
displayName: 'Petition ID',
name: 'petitionId',
description: 'ID of the petition to update.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'petition',
],
operation: [
'update',
],
},
},
},
makeSimpleField('petition', 'update'),
{
displayName: 'Update Fields',
name: 'updateFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'petition',
],
operation: [
'update',
],
},
},
options: petitionAdditionalFieldsOptions,
},
] as INodeProperties[];

View File

@@ -0,0 +1,376 @@
import {
Operation,
Resource,
} from '../types';
export const languageOptions = [
{
name: 'Danish',
value: 'da',
},
{
name: 'Dutch',
value: 'nl',
},
{
name: 'English',
value: 'en',
},
{
name: 'Finnish',
value: 'fi',
},
{
name: 'French',
value: 'fr',
},
{
name: 'German',
value: 'de',
},
{
name: 'Hungarian',
value: 'hu',
},
{
name: 'Indonesian',
value: 'id',
},
{
name: 'Japanese',
value: 'ja',
},
{
name: 'Portuguese - Portugal',
value: 'pt',
},
{
name: 'Portuguese - Brazil',
value: 'br',
},
{
name: 'Rumanian',
value: 'ru',
},
{
name: 'Spanish',
value: 'es',
},
{
name: 'Swedish',
value: 'sv',
},
{
name: 'Turkish',
value: 'tr',
},
{
name: 'Welsh',
value: 'cy',
},
] as const;
const postalAddressesFields = [
{
displayName: 'Primary',
name: 'primary',
type: 'boolean',
default: false,
description: 'Whether this is the person\'s primary address.',
},
{
displayName: 'Address Line',
name: 'address_lines',
type: 'string', // The Action Network API expects a string array but ignores any string beyond the first, so this input field is simplified to string.
default: '',
description: 'Line for a person\'s address.',
},
{
displayName: 'Locality',
name: 'locality',
type: 'string',
default: '',
description: 'City or other local administrative area. If blank, this will be filled in based on Action Network\'s geocoding.',
},
{
displayName: 'Region',
name: 'region',
type: 'string',
default: '',
description: 'State or subdivision code per ISO 3166-2.',
},
{
displayName: 'Postal Code',
name: 'postal_code',
type: 'string',
default: '',
description: 'Region specific postal code, such as ZIP code.',
},
{
displayName: 'Country',
name: 'country',
type: 'string',
default: '',
description: 'Country code according to ISO 3166-1 Alpha-2. Defaults to US.',
},
{
displayName: 'Language',
name: 'language',
type: 'string',
default: '',
description: 'Language in which the address is recorded, per ISO 639.',
},
{
displayName: 'Location',
name: 'location',
type: 'fixedCollection',
default: '',
options: [
{
displayName: 'Location Fields',
name: 'location_fields',
values: [
{
displayName: 'Latitude',
name: 'latitude',
type: 'string',
default: '',
description: 'Latitude of the location of the address.',
},
{
displayName: 'Longitude',
name: 'longitude',
type: 'string',
default: '',
description: 'Longitude of the location of the address.',
},
],
},
],
},
];
export const eventAdditionalFieldsOptions = [
{
displayName: 'Browser URL',
name: 'browser_url',
type: 'string',
default: '',
description: 'URL to this events page on the Action Network or a third party.',
},
{
displayName: 'Description',
name: 'description',
type: 'string',
default: '',
description: 'Description of the event. HTML supported.',
},
{
displayName: 'End Date',
name: 'end_date',
type: 'dateTime',
default: '',
description: 'End date and time of the event.',
},
{
displayName: 'Featured Image URL',
name: 'featured_image_url',
type: 'string',
default: '',
description: 'URL to this events featured image on the Action Network.',
},
{
displayName: 'Instructions',
name: 'instructions',
type: 'string',
default: '',
description: 'Event\'s instructions for activists, visible after they RSVP. HTML supported.',
},
{
displayName: 'Location',
name: 'location',
type: 'fixedCollection',
default: {},
placeholder: 'Add Location Field',
typeOptions: {
multipleValues: false,
},
options: [
// different name, identical structure
{
displayName: 'Postal Addresses Fields',
name: 'postal_addresses_fields',
placeholder: 'Add Postal Address Field',
values: postalAddressesFields,
},
],
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'Internal (not public) title of the event.',
},
{
displayName: 'Start Date',
name: 'start_date',
type: 'dateTime',
default: '',
description: 'Start date and time of the event.',
},
];
export const personAdditionalFieldsOptions = [
{
displayName: 'Family Name',
name: 'family_name',
type: 'string',
default: '',
description: 'Persons last name.',
},
{
displayName: 'Given Name',
name: 'given_name',
type: 'string',
default: '',
description: 'Persons first name.',
},
{
displayName: 'Language Spoken',
name: 'languages_spoken',
type: 'options', // Action Network accepts a `string[]` of language codes, but supports only one language per person - sending an array of 2+ languages will result in the first valid language being set as the preferred language for the person. Therefore, the user may select only one option in the n8n UI.
default: [],
description: 'Language spoken by the person',
options: languageOptions,
},
{
displayName: 'Phone Number', // on create, only _one_ must be passed in
name: 'phone_numbers',
type: 'fixedCollection',
default: {},
placeholder: 'Add Phone Numbers Field',
options: [
{
displayName: 'Phone Numbers Fields',
name: 'phone_numbers_fields',
placeholder: 'Add Phone Number Field',
values: [
{
displayName: 'Number',
name: 'number',
type: 'string',
default: '',
description: 'Person\'s mobile number, in international format without the plus sign.',
},
{
displayName: 'Primary',
name: 'primary',
type: 'hidden',
default: true,
description: 'Whether this is the person\'s primary phone number.',
},
{
displayName: 'Status',
name: 'status',
type: 'options',
default: 'subscribed',
description: 'Subscription status of this number.',
options: [
{
name: 'Bouncing',
value: 'bouncing',
},
{
name: 'Previous Bounce',
value: 'previous bounce',
},
{
name: 'Subscribed',
value: 'subscribed',
},
{
name: 'Unsubscribed',
value: 'unsubscribed',
},
],
},
],
},
],
},
{
displayName: 'Postal Addresses',
name: 'postal_addresses',
type: 'fixedCollection',
default: {},
placeholder: 'Add Postal Addresses Field',
typeOptions: {
multipleValues: true,
},
options: [
{
displayName: 'Postal Addresses Fields',
name: 'postal_addresses_fields',
placeholder: 'Add Postal Address Field',
values: postalAddressesFields,
},
],
},
];
export const petitionAdditionalFieldsOptions = [
{
displayName: 'Browser URL',
name: 'browser_url',
type: 'string',
default: '',
description: 'URL to this petitions page on the Action Network or a third party.',
},
{
displayName: 'Featured Image URL',
name: 'featured_image_url',
type: 'string',
default: '',
description: 'URL to this actions featured image on the Action Network.',
},
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
description: 'Internal (not public) title of the petition.',
},
{
displayName: 'Petition Text',
name: 'petition_text',
type: 'string',
default: '',
description: 'Text of the letter to the petitions target.',
},
{
displayName: 'Targets',
name: 'target',
type: 'string',
default: '',
description: 'Comma-separated names of targets for this petition.',
},
];
export const makeSimpleField = (resource: Resource, operation: Operation) => ({
displayName: 'Simple',
name: 'simple',
type: 'boolean',
displayOptions: {
show: {
resource: [
resource,
],
operation: [
operation,
],
},
},
default: true,
description: 'Return a simplified version of the response instead of the raw data.',
});

View File

@@ -0,0 +1,282 @@
import {
INodeProperties,
} from 'n8n-workflow';
import {
makeSimpleField,
} from './SharedFields';
export const signatureOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'signature',
],
},
},
options: [
{
name: 'Create',
value: 'create',
},
{
name: 'Get',
value: 'get',
},
{
name: 'Get All',
value: 'getAll',
},
{
name: 'Update',
value: 'update',
},
],
default: 'create',
description: 'Operation to perform',
},
] as INodeProperties[];
export const signatureFields = [
// ----------------------------------------
// signature: create
// ----------------------------------------
{
displayName: 'Petition ID',
name: 'petitionId',
description: 'ID of the petition to sign.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'signature',
],
operation: [
'create',
],
},
},
},
{
displayName: 'Person ID',
name: 'personId',
description: 'ID of the person whose signature to create.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'signature',
],
operation: [
'create',
],
},
},
},
makeSimpleField('signature', 'create'),
{
displayName: 'Additional Fields',
name: 'additionalFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'signature',
],
operation: [
'create',
],
},
},
options: [
{
displayName: 'Comments',
name: 'comments',
type: 'string',
default: '',
description: 'Comments to leave when signing this petition.',
},
],
},
// ----------------------------------------
// signature: get
// ----------------------------------------
{
displayName: 'Petition ID',
name: 'petitionId',
description: 'ID of the petition whose signature to retrieve.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'signature',
],
operation: [
'get',
],
},
},
},
{
displayName: 'Signature ID',
name: 'signatureId',
description: 'ID of the signature to retrieve.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'signature',
],
operation: [
'get',
],
},
},
},
makeSimpleField('signature', 'get'),
// ----------------------------------------
// signature: getAll
// ----------------------------------------
{
displayName: 'Petition ID',
name: 'petitionId',
description: 'ID of the petition whose signatures to retrieve.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'signature',
],
operation: [
'getAll',
],
},
},
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
description: 'Return all results.',
displayOptions: {
show: {
resource: [
'signature',
],
operation: [
'getAll',
],
},
},
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 50,
description: 'The number of results to return.',
typeOptions: {
minValue: 1,
},
displayOptions: {
show: {
resource: [
'signature',
],
operation: [
'getAll',
],
returnAll: [
false,
],
},
},
},
makeSimpleField('signature', 'getAll'),
// ----------------------------------------
// signature: update
// ----------------------------------------
{
displayName: 'Petition ID',
name: 'petitionId',
description: 'ID of the petition whose signature to update.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'signature',
],
operation: [
'update',
],
},
},
},
{
displayName: 'Signature ID',
name: 'signatureId',
description: 'ID of the signature to update.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'signature',
],
operation: [
'update',
],
},
},
},
makeSimpleField('signature', 'update'),
{
displayName: 'Update Fields',
name: 'updateFields',
type: 'collection',
placeholder: 'Add Field',
default: {},
displayOptions: {
show: {
resource: [
'signature',
],
operation: [
'update',
],
},
},
options: [
{
displayName: 'Comments',
name: 'comments',
type: 'string',
default: '',
description: 'Comments to leave when signing this petition.',
},
],
},
] as INodeProperties[];

View File

@@ -0,0 +1,131 @@
import {
INodeProperties,
} from 'n8n-workflow';
import {
makeSimpleField,
} from './SharedFields';
export const tagOperations = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
displayOptions: {
show: {
resource: [
'tag',
],
},
},
options: [
{
name: 'Create',
value: 'create',
},
{
name: 'Get',
value: 'get',
},
{
name: 'Get All',
value: 'getAll',
},
],
default: 'create',
description: 'Operation to perform',
},
] as INodeProperties[];
export const tagFields = [
// ----------------------------------------
// tag: create
// ----------------------------------------
{
displayName: 'Name',
name: 'name',
description: 'Name of the tag to create.',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'tag',
],
operation: [
'create',
],
},
},
},
makeSimpleField('tag', 'create'),
// ----------------------------------------
// tag: get
// ----------------------------------------
{
displayName: 'Tag ID',
name: 'tagId',
description: 'ID of the tag to retrieve.',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
resource: [
'tag',
],
operation: [
'get',
],
},
},
},
makeSimpleField('tag', 'get'),
// ----------------------------------------
// tag: getAll
// ----------------------------------------
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
default: false,
description: 'Return all results.',
displayOptions: {
show: {
resource: [
'tag',
],
operation: [
'getAll',
],
},
},
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
default: 50,
description: 'The number of results to return.',
typeOptions: {
minValue: 1,
},
displayOptions: {
show: {
resource: [
'tag',
],
operation: [
'getAll',
],
returnAll: [
false,
],
},
},
},
makeSimpleField('tag', 'getAll'),
] as INodeProperties[];

View File

@@ -0,0 +1,7 @@
export * from './AttendanceDescription';
export * from './EventDescription';
export * from './PersonDescription';
export * from './PersonTagDescription';
export * from './PetitionDescription';
export * from './SignatureDescription';
export * from './TagDescription';

View File

@@ -0,0 +1,99 @@
import { languageOptions } from './descriptions/SharedFields';
export type Resource = 'attendance' | 'event' | 'person' | 'personTag' | 'petition' | 'signature' | 'tag';
export type Operation = 'create' | 'delete' | 'get' | 'getAll' | 'update' | 'add' | 'remove';
export type LanguageCodes = typeof languageOptions[number]['value']
// ----------------------------------------
// UI fields
// ----------------------------------------
export type AllFieldsUi = {
email_addresses: EmailAddressUi;
postal_addresses: PostalAddressesUi;
phone_numbers: PhoneNumberUi;
languages_spoken: LanguageCodes;
target: string;
location: LocationUi;
}
export type EmailAddressUi = {
email_addresses_fields: EmailAddressField,
}
export type EmailAddressField = {
primary: boolean;
address: string;
status: EmailStatus;
}
type BaseStatus = 'subscribed' | 'unsubscribed' | 'bouncing' | 'previous bounce';
type EmailStatus = BaseStatus | 'spam complaint' | 'previous spam complaint';
type PhoneNumberUi = {
phone_numbers_fields: PhoneNumberField[],
}
export type PhoneNumberField = {
primary: boolean;
number: string;
status: BaseStatus;
};
type PostalAddressesUi = {
postal_addresses_fields: PostalAddressField[],
}
type LocationUi = {
postal_addresses_fields: PostalAddressField,
}
export type PostalAddressField = {
primary: boolean;
address_lines: string;
locality: string;
region: string;
postal_code: string;
country: string;
language: LanguageCodes;
location: { location_fields: LatitudeLongitude }
}
type LatitudeLongitude = {
latitude: string;
longitude: string;
}
export type FieldWithPrimaryField = EmailAddressField | PhoneNumberField | PostalAddressField;
// ----------------------------------------
// responses
// ----------------------------------------
export type LinksFieldContainer = { _links: { self: { href: string } } };
export type Response = JsonObject & LinksFieldContainer;
export type PersonResponse = Response & {
identifiers: string[];
email_addresses: EmailAddressField[];
phone_numbers: PhoneNumberField[];
postal_addresses: PostalAddressField[];
languages_spoken: LanguageCodes[];
};
export type PetitionResponse = Response & { _embedded: { 'osdi:creator': PersonResponse } };
// ----------------------------------------
// utils
// ----------------------------------------
export type JsonValue = string | number | boolean | null | JsonObject | JsonValue[];
export type JsonObject = { [key: string]: JsonValue };

View File

@@ -27,6 +27,11 @@
"icon": "☀️",
"url": "https://n8n.io/blog/2021-the-year-to-automate-the-new-you-with-n8n/"
},
{
"label": "How to build a low-code, self-hosted URL shortener in 3 steps",
"icon": "🔗",
"url": "https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/"
},
{
"label": "15 Google apps you can combine and automate to increase productivity",
"icon": "💡",

View File

@@ -16,7 +16,7 @@ export class AwsLambda implements INodeType {
description: INodeTypeDescription = {
displayName: 'AWS Lambda',
name: 'awsLambda',
icon: 'file:lambda.png',
icon: 'file:lambda.svg',
group: ['output'],
version: 1,
subtitle: '={{$parameter["function"]}}',

View File

@@ -16,7 +16,7 @@ export class AwsSns implements INodeType {
description: INodeTypeDescription = {
displayName: 'AWS SNS',
name: 'awsSns',
icon: 'file:sns.png',
icon: 'file:sns.svg',
group: ['output'],
version: 1,
subtitle: '={{$parameter["topic"]}}',

View File

@@ -26,7 +26,7 @@ export class AwsSnsTrigger implements INodeType {
displayName: 'AWS SNS Trigger',
subtitle: `={{$parameter["topic"].split(':')[5]}}`,
name: 'awsSnsTrigger',
icon: 'file:sns.png',
icon: 'file:sns.svg',
group: ['trigger'],
version: 1,
description: 'Handle AWS SNS events via webhooks',

View File

@@ -51,7 +51,7 @@ export class AwsS3 implements INodeType {
description: INodeTypeDescription = {
displayName: 'AWS S3',
name: 'awsS3',
icon: 'file:s3.png',
icon: 'file:s3.svg',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
@@ -163,6 +163,15 @@ export class AwsS3 implements INodeType {
returnData.push({ success: true });
}
// https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucket.html
if (operation === 'delete') {
const name = this.getNodeParameter('name', i) as string;
responseData = await awsApiRequestSOAP.call(this, `${name}.s3`, 'DELETE', '', '', {}, headers);
returnData.push({ success: true });
}
//https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html
if (operation === 'getAll') {
const returnAll = this.getNodeParameter('returnAll', 0) as boolean;

View File

@@ -20,6 +20,11 @@ export const bucketOperations = [
value: 'create',
description: 'Create a bucket',
},
{
name: 'Delete',
value: 'delete',
description: 'Delete a bucket',
},
{
name: 'Get All',
value: 'getAll',
@@ -152,6 +157,29 @@ export const bucketFields = [
},
],
},
/* -------------------------------------------------------------------------- */
/* bucket:delete */
/* -------------------------------------------------------------------------- */
{
displayName: 'Name',
name: 'name',
type: 'string',
required: true,
default: '',
displayOptions: {
show: {
resource: [
'bucket',
],
operation: [
'delete',
],
},
},
description: 'Name of the AWS S3 bucket to delete.',
},
/* -------------------------------------------------------------------------- */
/* bucket:getAll */
/* -------------------------------------------------------------------------- */

View File

@@ -57,7 +57,7 @@ export async function awsApiRequest(this: IHookFunctions | IExecuteFunctions | I
try {
return await this.helpers.request!(options);
} catch (error) {
throw new NodeApiError(this.getNode(), error, { parseXml: true });
throw new NodeApiError(this.getNode(), error);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<svg width="2065" height="2500" viewBox="0 0 256 310" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M20.624 53.686L0 64v181.02l20.624 10.254.124-.149V53.828l-.124-.142" fill="#8C3123"/><path d="M131 229L20.624 255.274V53.686L131 79.387V229" fill="#E05243"/><path d="M81.178 187.866l46.818 5.96.294-.678.263-76.77-.557-.6-46.818 5.874v66.214" fill="#8C3123"/><path d="M127.996 229.295l107.371 26.035.169-.269-.003-201.195-.17-.18-107.367 25.996v149.613" fill="#8C3123"/><path d="M174.827 187.866l-46.831 5.96v-78.048l46.831 5.874v66.214" fill="#E05243"/><path d="M174.827 89.631l-46.831 8.535-46.818-8.535 46.759-12.256 46.89 12.256" fill="#5E1F18"/><path d="M174.827 219.801l-46.831-8.591-46.818 8.591 46.761 13.053 46.888-13.053" fill="#F2B0A9"/><path d="M81.178 89.631l46.818-11.586.379-.117V.313L127.996 0 81.178 23.413v66.218" fill="#8C3123"/><path d="M174.827 89.631l-46.831-11.586V0l46.831 23.413v66.218" fill="#E05243"/><path d="M127.996 309.428l-46.823-23.405v-66.217l46.823 11.582.689.783-.187 75.906-.502 1.351" fill="#8C3123"/><path d="M127.996 309.428l46.827-23.405v-66.217l-46.827 11.582v78.04M235.367 53.686L256 64v181.02l-20.633 10.31V53.686" fill="#E05243"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -426,6 +426,7 @@ export class AwsTranscribe implements INodeType {
Media: {
MediaFileUri: mediaFileUri,
},
Settings: {},
};
if (detectLang) {
@@ -438,16 +439,16 @@ export class AwsTranscribe implements INodeType {
Object.assign(body.Settings, { ChannelIdentification: options.channelIdentification });
}
if (options.MaxAlternatives) {
if (options.maxAlternatives) {
Object.assign(body.Settings, {
ShowAlternatives: options.maxAlternatives,
ShowAlternatives: true,
MaxAlternatives: options.maxAlternatives,
});
}
}
if (options.showSpeakerLabels) {
if (options.maxSpeakerLabels) {
Object.assign(body.Settings, {
ShowSpeakerLabels: options.showSpeakerLabels,
ShowSpeakerLabels: true,
MaxSpeakerLabels: options.maxSpeakerLabels,
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg height="2500" viewBox="-3.023 -0.22 420.923 433.54" width="2443" xmlns="http://www.w3.org/2000/svg"><path d="M208.45 227.89c-1.59 2.26-2.93 4.12-4.22 6q-30.86 45.42-61.7 90.83-28.69 42.24-57.44 84.43a3.88 3.88 0 01-2.73 1.59q-40.59-.35-81.16-.88c-.3 0-.61-.09-1.2-.18a14.44 14.44 0 01.76-1.65q28.31-43.89 56.62-87.76 25.11-38.88 50.25-77.74 27.86-43.18 55.69-86.42c2.74-4.25 5.59-8.42 8.19-12.75a5.26 5.26 0 00.56-3.83c-5-15.94-10.1-31.84-15.19-47.74-2.18-6.81-4.46-13.58-6.5-20.43-.66-2.2-1.75-2.87-4-2.86-17 .07-33.9.05-50.85.05-3.22 0-3.23 0-3.23-3.18 0-20.84 0-41.68-.06-62.52 0-2.32.76-2.84 2.94-2.84q51.19.09 102.4 0a3.29 3.29 0 013.6 2.43q27 67.91 54 135.77 31.5 79.14 63 158.3c6.52 16.38 13.09 32.75 19.54 49.17.77 2 1.57 2.38 3.59 1.76 17.89-5.53 35.82-10.91 53.7-16.45 2.25-.7 3.07-.23 3.77 2 6.1 19.17 12.32 38.3 18.5 57.45.21.66.37 1.33.62 2.25-1.28.47-2.48 1-3.71 1.34q-61 19.33-121.93 38.68c-1.94.61-2.52-.05-3.17-1.68q-18.61-47.16-37.31-94.28-18.29-46.14-36.6-92.28c-1.83-4.62-3.63-9.26-5.46-13.88-.29-.79-.69-1.48-1.27-2.7z" fill="#fa7e14"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 B

View File

@@ -0,0 +1 @@
<svg width="2490" height="2500" viewBox="0 0 256 257" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M98.875 232.033l-26.433-7.408-25.001-28.508 31.272-.863 20.162 36.779m-61.125-18.8l-14.875-4.166-14.058-16.034 17.082-2.809 11.851 23.009" fill="#995B80"/><path d="M0 191.017l15.204 3.091 2.207-3.882V58.503l-2.207-2.561L0 64.6v126.417" fill="#7B3F65"/><path d="M73.933 69.708L15.208 55.942v138.166l8.798-.818 13.744 19.943 10.6-22.205 25.583-2.378V69.708" fill="#C17B9D"/><path d="M33.958 198.133l26.063 5.25 1.716-4.045V37.44l-1.716-3.665-26.063 13.208v151.15" fill="#7B3F65"/><path d="M208.734 81.516L60.021 33.775v169.612l17.221-2.216 21.633 30.862 17.126-35.85 92.733-11.933V81.516" fill="#C17B9D"/><path d="M181.833 256.492l-37.566-10.525-35.509-40.5 46.033-.468 27.042 51.493" fill="#995B80"/><path d="M89.591 208.95l38.33 7.417 2.977-2.566V4.117L127.921 0l-38.33 19.158V208.95" fill="#7B3F65"/><path d="M256 64.033L127.925 0v216.367l22.597-4.528 31.311 44.653 26.901-56.309-.017-.002L256 190.708V64.033" fill="#C17B9D"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -21,7 +21,7 @@ export class BitbucketTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Bitbucket Trigger',
name: 'bitbucketTrigger',
icon: 'file:bitbucket.png',
icon: 'file:bitbucket.svg',
group: ['trigger'],
version: 1,
description: 'Handle Bitbucket events via webhooks',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1002 B

View File

@@ -0,0 +1 @@
<svg width="48" height="48" xmlns="http://www.w3.org/2000/svg" fill="none"><defs><linearGradient y2=".656" x2=".33" y1="-.17" x1=".762" id="a"><stop stop-color="#0052CC" offset=".18"/><stop stop-color="#2684FF" offset="1"/></linearGradient></defs><path stroke="null" fill="#2684FF" d="M1.721 3.873a1.465 1.465 0 011.129-.509l42.772.007a1.466 1.466 0 011.466 1.7l-6.222 38.193a1.466 1.466 0 01-1.465 1.231H9.555a1.993 1.993 0 01-1.95-1.663L1.386 5.064c-.07-.427.053-.863.336-1.191zm17.8 26.787h9.526l2.308-13.475H16.941l2.58 13.475z" clip-rule="evenodd" fill-rule="evenodd"/><path stroke="null" fill="url(#a)" d="M44.418 18.437h-12.64l-2.12 12.383h-8.755L10.567 43.09c.327.283.745.44 1.178.444H39.18a1.347 1.347 0 001.347-1.132l3.892-23.965z"/></svg>

After

Width:  |  Height:  |  Size: 749 B

View File

@@ -25,7 +25,7 @@ export class Bitly implements INodeType {
description: INodeTypeDescription = {
displayName: 'Bitly',
name: 'bitly',
icon: 'file:bitly.png',
icon: 'file:bitly.svg',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -0,0 +1 @@
<svg viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><g clip-rule="evenodd" fill-rule="evenodd"><path fill="none" d="M0 0h128v128H0z"/><path d="M63.708 0C28.443 0 0 28.306 0 65.322c0 19.193 10.266 37.736 24.703 48.877 2.78 2.145 6.091 1.949 7.989.109 1.599-1.55 1.467-5.282-1.507-7.897-11.541-10.149-19.52-25.514-19.52-40.861 0-27.564 24.474-50.756 52.044-50.756 33.558 0 51.503 27.252 51.503 50.351 0 14.114-6.902 31.155-19.361 42.025.019-.049 2.588-5.101 2.588-14.94 0-16.748-10.612-25.821-22.926-25.821-8.914 0-14.251 3.187-17.883 6.158 0-6.822.228-19.563.228-19.563 0-8.409-2.946-15.14-13.213-15.287-5.943-.084-10.353 2.641-13.103 8.803-.991 2.311-.626 4.822 1.333 5.96 1.621.941 4.287.243 5.606-1.517.881-1.1 1.375-1.335 2.141-1.252 1.261.136 1.309 2.168 1.359 3.468.038.998 1.008 15.494.477 52.746 0 10.275 8.059 22.077 27.355 22.077 8.307 0 14.686-2.322 23.953-7.587C107.871 112.398 128 94.62 128 64.55 128 26.977 97.572 0 63.708 0zm6.052 113.602c-7.183.126-12.422-2.183-12.743-8.461-.12-2.356-.082-4.865.033-6.378.695-9.174 7.11-15.774 13.443-17.041 7.876-1.576 13.123 2.026 13.123 12.337-.001 6.968-1.935 19.334-13.856 19.543z" fill="#DD5A2B"/></g></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -16,6 +16,13 @@
{
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.calendlyTrigger/"
}
],
"generic": [
{
"label": "5 tasks you can automate with the new Notion API ",
"icon": "⚡️",
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
}
]
}
}

View File

@@ -17,7 +17,7 @@ export class CalendlyTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Calendly Trigger',
name: 'calendlyTrigger',
icon: 'file:calendly.png',
icon: 'file:calendly.svg',
group: ['trigger'],
version: 1,
description: 'Starts the workflow when Calendly events occur.',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 924 B

View File

@@ -0,0 +1 @@
<svg height="2500" viewBox="7.4 0 344.6 360" width="2342" xmlns="http://www.w3.org/2000/svg"><g fill="#676b74"><path d="M313.8 360H45.5c-21 0-38.1-17.1-38.1-38.1V53.5c0-21 17.1-38.1 38.1-38.1h268.3c21 0 38.1 17.1 38.1 38.1v268.3c.1 21.1-17 38.2-38.1 38.2zM45.5 36.5c-9.4 0-17 7.6-17 17v268.3c0 9.4 7.6 17 17 17h268.3c9.4 0 17-7.6 17-17V53.5c0-9.4-7.6-17-17-17z"/><path d="M256.6 72.4c-4.5 0-8.1-3.6-8.1-8.1V8.1c0-4.5 3.6-8.1 8.1-8.1s8.1 3.6 8.1 8.1v56.1c0 4.5-3.6 8.2-8.1 8.2zm-154.7 0c-4.5 0-8.1-3.6-8.1-8.1V8.1c0-4.5 3.6-8.1 8.1-8.1s8.1 3.6 8.1 8.1v56.1c.1 4.5-3.6 8.2-8.1 8.2zm87.5 181.4c-33.6 0-60.9-27.3-60.9-60.9s27.3-60.9 60.9-60.9c15.2 0 29.7 5.6 40.9 15.8 1.4 1.2 1.5 3.4.2 4.7-1.2 1.4-3.4 1.5-4.7.2-10-9.1-22.9-14.1-36.4-14.1-29.9 0-54.2 24.3-54.2 54.2s24.3 54.2 54.2 54.2c13.5 0 26.4-5 36.4-14.1 1.4-1.2 3.5-1.1 4.7.2 1.2 1.4 1.1 3.5-.2 4.7-11.2 10.4-25.7 16-40.9 16z"/></g></svg>

After

Width:  |  Height:  |  Size: 891 B

View File

@@ -1 +1 @@
<svg viewBox="-10 0 155 155" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient x1="0%" y1="68.01%" y2="68.01%" id="a"><stop stop-color="#8930FD" offset="0%"/><stop stop-color="#49CCF9" offset="100%"/></linearGradient><linearGradient x1="0%" y1="68.01%" y2="68.01%" id="b"><stop stop-color="#FF02F0" offset="0%"/><stop stop-color="#FFC800" offset="100%"/></linearGradient></defs><g fill="none"><path d="M.4 119.12l23.81-18.24C36.86 117.39 50.3 125 65.26 125c14.88 0 27.94-7.52 40.02-23.9l24.15 17.8C112 142.52 90.34 155 65.26 155c-25 0-46.87-12.4-64.86-35.88z" fill="url(#a)"/><path fill="url(#b)" d="M65.18 39.84L22.8 76.36 3.21 53.64 65.27.16l61.57 53.52-19.68 22.64z"/></g></svg>
<svg viewBox="-10 0 155 155" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient x1="0%" y1="68.01%" y2="68.01%" id="a"><stop stop-color="#8930FD" offset="0%"/><stop stop-color="#49CCF9" offset="100%"/></linearGradient><linearGradient x1="0%" y1="68.01%" y2="68.01%" id="b"><stop stop-color="#FF02F0" offset="0%"/><stop stop-color="#FFC800" offset="100%"/></linearGradient></defs><g fill="none"><path d="M.4 119.12l23.81-18.24C36.86 117.39 50.3 125 65.26 125c14.88 0 27.94-7.52 40.02-23.9l24.15 17.8C112 142.52 90.34 155 65.26 155c-25 0-46.87-12.4-64.86-35.88z" fill="url(#a)"/><path fill="url(#b)" d="M65.18 39.84L22.8 76.36 3.21 53.64 65.27.16l61.57 53.52-19.68 22.64z"/></g></svg>

Before

Width:  |  Height:  |  Size: 689 B

After

Width:  |  Height:  |  Size: 688 B

View File

@@ -50,7 +50,7 @@ export class Clockify implements INodeType {
description: INodeTypeDescription = {
displayName: 'Clockify',
name: 'clockify',
icon: 'file:clockify.png',
icon: 'file:clockify.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',

View File

@@ -22,7 +22,7 @@ import { IWorkspaceDto } from './WorkpaceInterfaces';
export class ClockifyTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Clockify Trigger',
icon: 'file:clockify.png',
icon: 'file:clockify.svg',
name: 'clockifyTrigger',
group: [ 'trigger' ],
version: 1,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@@ -0,0 +1 @@
<svg width="256" height="256" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M14 0h227v256H14z"/><path fill-rule="evenodd" clip-rule="evenodd" d="M165.124 87.99l58.777-58.814 16.825 16.823-58.778 58.815-16.824-16.823zm-23.817 60.105c-11.503 0-20.822-9.36-20.822-20.918 0-11.546 9.319-20.918 20.822-20.918 11.503 0 20.822 9.372 20.822 20.918 0 11.558-9.319 20.918-20.822 20.918zM241 208.845l-16.824 16.835-58.778-58.816 16.825-16.835L241 208.845z" fill="#222"/><path fill-rule="evenodd" clip-rule="evenodd" d="M141.913 217.637c11.848 0 23.136-2.364 33.482-6.567l28.708 28.74C185.692 250.089 164.5 256 141.913 256 71.274 256 14 198.689 14 128.006 14 57.311 71.275 0 141.913 0c22.361 0 43.362 5.767 61.641 15.856l-28.231 28.261c-10.333-4.179-21.585-6.543-33.41-6.543-49.47 0-89.575 40.309-89.575 90.038 0 49.716 40.104 90.025 89.575 90.025z" fill="#03A9F4"/></svg>

After

Width:  |  Height:  |  Size: 882 B

View File

@@ -36,7 +36,7 @@ export class Coda implements INodeType {
description: INodeTypeDescription = {
displayName: 'Coda',
name: 'coda',
icon: 'file:coda.png',
icon: 'file:coda.svg',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
<svg width="34" height="54" xmlns="http://www.w3.org/2000/svg"><path d="M.69 48.887c0 2.489 1.557 4.184 3.704 4.184 1.896 0 3.095-1.082 3.328-2.687h-1.79c-.142.721-.733 1.118-1.52 1.118-1.199 0-1.932-1.1-1.932-2.615 0-1.516.733-2.616 1.932-2.616.787 0 1.378.397 1.52 1.118h1.79c-.233-1.605-1.432-2.687-3.328-2.687-2.147 0-3.703 1.695-3.703 4.185zm15.623 0c0-2.454-1.539-4.185-3.775-4.185-2.237 0-3.775 1.731-3.775 4.185 0 2.453 1.538 4.184 3.775 4.184 2.236 0 3.775-1.731 3.775-4.184zm-1.79 0c0 1.515-.697 2.633-1.985 2.633-1.289 0-1.986-1.118-1.986-2.633 0-1.516.697-2.634 1.986-2.634 1.288 0 1.986 1.118 1.986 2.634zm8.665 4.003h1.79V41.167h-1.79v4.402c-.358-.451-1.145-.866-2.021-.866-2.326 0-3.686 1.894-3.686 4.185 0 2.308 1.36 4.184 3.685 4.184.877 0 1.664-.415 2.022-.865v.685zm0-2.488a1.927 1.927 0 01-1.717 1.064c-1.378 0-2.2-1.1-2.2-2.58 0-1.479.822-2.579 2.2-2.579.733 0 1.413.433 1.717 1.064v3.03zM33.31 52.89v-5.248c0-1.822-1.181-2.94-3.185-2.94-1.628 0-2.88 1.028-3.113 2.399h1.718c.196-.56.644-.866 1.36-.866 1.019 0 1.52.631 1.52 1.443v.65c-.322-.235-1.127-.488-1.843-.488-1.753 0-3.095 1.065-3.095 2.58 0 1.641 1.342 2.615 2.97 2.615.895 0 1.7-.325 1.968-.595v.45h1.7zm-1.7-2.128c-.197.505-.912.812-1.628.812-.805 0-1.646-.343-1.646-1.154 0-.794.84-1.137 1.646-1.137.716 0 1.431.307 1.628.812v.667zM30.682.928H3.318A2.99 2.99 0 00.333 3.905v29.762a2.99 2.99 0 002.985 2.976h27.364a2.99 2.99 0 002.985-2.976v-1.39c-.05-1.785-.1-5.505-.1-7.192 0-.942-.696-1.736-1.592-1.736-.995 0-1.642.595-2.14 1.091-1.492 1.34-3.73 1.588-5.67 1.24-.896-.198-1.742-.446-2.439-.892-2.139-1.24-3.532-3.572-3.532-6.052 0-2.48 1.393-4.762 3.532-6.052.747-.446 1.592-.694 2.438-.892 1.89-.348 4.18-.1 5.672 1.24.547.496 1.194 1.09 2.14 1.09.895 0 1.591-.793 1.591-1.735 0-1.637.05-5.407.1-7.193v-1.29A2.99 2.99 0 0030.682.929z" fill="#F46A54"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -32,7 +32,7 @@ export class CoinGecko implements INodeType {
description: INodeTypeDescription = {
displayName: 'CoinGecko',
name: 'coinGecko',
icon: 'file:coinGecko.png',
icon: 'file:coinGecko.svg',
group: ['output'],
version: 1,
description: 'Consume CoinGecko API',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 276 276"><defs><style>.cls-3{fill:#fff}.cls-4{fill:#8bc53f}.cls-6{fill:#58595b}</style></defs><g id="Coin_Gecko_AI" data-name="Coin Gecko AI"><path d="M276 137.39A138 138 0 11137.39 0 138 138 0 01276 137.39z" fill="#8dc63f"/><path d="M265.65 137.44a127.63 127.63 0 11-128.21-127 127.65 127.65 0 01128.21 127z" fill="#f9e988"/><path class="cls-3" d="M140.35 18.66a70.18 70.18 0 0124.53 0 74.75 74.75 0 0123.43 7.85c7.28 4 13.57 9.43 19.83 14.52s12.49 10.3 18.42 16a93.32 93.32 0 0115.71 19 108.28 108.28 0 0111 22.17c5.33 15.66 7.18 32.53 4.52 48.62H257c-2.67-15.95-6.29-31.15-12-45.61A177.51 177.51 0 00235.56 80a209.1 209.1 0 00-12.42-20 72.31 72.31 0 00-16.64-16.8c-6.48-4.62-13.93-7.61-21.14-10.45S171 27 163.48 24.84s-15.16-3.78-23.14-5.35z"/><path class="cls-4" d="M202.74 92.39c-9.26-2.68-18.86-6.48-28.58-10.32-.56-2.44-2.72-5.48-7.09-9.19-6.35-5.51-18.28-5.37-28.59-2.93-11.38-2.68-22.62-3.63-33.41-1-88.25 24.31-38.21 83.62-70.61 143.24 4.61 9.78 54.3 66.84 126.2 51.53 0 0-24.59-59.09 30.9-87.45 45.01-23.09 77.53-65.81 11.18-83.88z"/><path class="cls-3" d="M213.64 131.2a5.35 5.35 0 11-5.38-5.32 5.36 5.36 0 015.38 5.32z"/><path d="M138.48 69.91c6.43.46 29.68 8 35.68 12.12-5-14.5-21.83-16.43-35.68-12.12z" fill="#009345"/><path class="cls-3" d="M144.6 106.58a24.68 24.68 0 11-24.69-24.67 24.68 24.68 0 0124.68 24.66z"/><path class="cls-6" d="M137.28 106.8a17.36 17.36 0 11-17.36-17.36 17.36 17.36 0 0117.36 17.36z"/><path class="cls-4" d="M233.63 142.08c-20 14.09-42.74 24.78-75 24.78-15.1 0-18.16-16-28.14-8.18-5.15 4.06-23.31 13.14-37.72 12.45S55 162 48.49 131.23C45.91 162 44.59 184.65 33 210.62c23 36.83 77.84 65.24 127.62 53-5.31-37.35 27.38-73.93 45.72-92.62 7-7.09 20.3-18.66 27.29-28.91z"/><path class="cls-6" d="M232.85 143c-6.21 5.66-13.6 9.85-21.12 13.55a134.9 134.9 0 01-23.7 8.63c-8.16 2.11-16.67 3.7-25.29 2.92s-17.43-3.71-23.14-10.17l.27-.31c7 4.54 15.08 6.14 23.12 6.37a108.27 108.27 0 0024.3-2 132.71 132.71 0 0023.61-7.3c7.63-3.15 15.18-6.8 21.68-12z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@@ -44,7 +44,7 @@ export class ConvertKit implements INodeType {
description: INodeTypeDescription = {
displayName: 'ConvertKit',
name: 'convertKit',
icon: 'file:convertKit.png',
icon: 'file:convertKit.svg',
group: ['input'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',

View File

@@ -24,7 +24,7 @@ export class ConvertKitTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'ConvertKit Trigger',
name: 'convertKitTrigger',
icon: 'file:convertKit.png',
icon: 'file:convertKit.svg',
subtitle: '={{$parameter["event"]}}',
group: ['trigger'],
version: 1,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg width="172" height="160" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M82.72 126.316c29.77 0 52.78-22.622 52.78-50.526 0-26.143-21.617-42.106-35.935-42.106-19.945 0-35.93 14.084-38.198 34.988-.418 3.856-3.476 7.09-7.355 7.061-6.423-.046-15.746-.1-21.658-.08-2.555.008-4.669-2.065-4.543-4.618.89-18.123 6.914-35.07 18.402-48.087C58.976 8.488 77.561 0 99.565 0c36.969 0 71.869 33.786 71.869 75.79 0 46.508-38.312 84.21-87.927 84.21-35.384 0-71.021-23.258-83.464-55.775a.702.702 0 01-.03-.377c.165-.962.494-1.841.818-2.707.471-1.258.931-2.488.864-3.906l-.215-4.529a5.523 5.523 0 013.18-5.263l1.798-.842a6.982 6.982 0 003.912-5.075 6.993 6.993 0 016.887-5.736c5.282 0 9.875 3.515 11.59 8.512 8.307 24.212 21.511 42.014 53.873 42.014z" fill="#FB6970"/></svg>

After

Width:  |  Height:  |  Size: 769 B

View File

@@ -58,6 +58,11 @@
"icon": "⚙️",
"url": "https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/"
},
{
"label": "5 tasks you can automate with the new Notion API ",
"icon": "⚡️",
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
},
{
"label": "Celebrating World Poetry Day with a daily poem in Telegram",
"icon": "📜",
@@ -123,7 +128,7 @@
"alias": [
"Time",
"Scheduler",
"Poll"
"Polling"
],
"subcategories": {
"Core Nodes": [

View File

@@ -12,6 +12,13 @@
{
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.crypto/"
}
],
"generic": [
{
"label": "How to build a low-code, self-hosted URL shortener in 3 steps",
"icon": "🔗",
"url": "https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/"
}
]
},
"alias": [

View File

@@ -34,7 +34,7 @@ export class CustomerIo implements INodeType {
description: INodeTypeDescription = {
displayName: 'Customer.io',
name: 'customerIo',
icon: 'file:customerio.png',
icon: 'file:customerio.svg',
group: ['output'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',

View File

@@ -29,7 +29,7 @@ export class CustomerIoTrigger implements INodeType {
displayName: 'Customer.io Trigger',
name: 'customerIoTrigger',
group: ['trigger'],
icon: 'file:customerio.png',
icon: 'file:customerio.svg',
version: 1,
description: 'Starts the workflow on a Customer.io update. (Beta)',
defaults: {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg width="256" height="180" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid"><path d="M127.843 102.423a51.21 51.21 0 10-51.21-51.186 51.186 51.186 0 0051.21 51.186z" fill="#FFCD00"/><path d="M128.161 128.016h-.171c-35.127-.009-65.777-23.834-74.453-57.872-2.571-10.238-10.898-18.907-21.454-18.907H0c0 70.686 57.303 127.99 127.99 127.99h.171v-51.211z" fill="#00ECBB"/><path d="M127.843 128.016h.147c35.13 0 65.785-23.829 74.452-57.872 2.597-10.238 10.923-18.907 21.48-18.907h32.082c-.013 70.69-57.323 127.99-128.014 127.99h-.147v-51.211z" fill="#AF64FF"/><path d="M218.509 141.73a127.99 127.99 0 01-181.013 0l36.197-36.197c30 29.98 78.618 29.98 108.618 0l36.198 36.198z" fill="#7131FF"/></svg>

After

Width:  |  Height:  |  Size: 710 B

View File

@@ -5,6 +5,7 @@ import {
INodeExecutionData,
INodeType,
INodeTypeDescription,
JsonObject,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow';
@@ -95,7 +96,7 @@ export class Discord implements INodeType {
} while (--maxTries);
if (maxTries <= 0) {
throw new NodeApiError(this.getNode(), { request: options }, { message: 'Could not send message. Max. amount of rate-limit retries got reached.' });
throw new NodeApiError(this.getNode(), { request: options } as JsonObject, { message: 'Could not send message. Max. amount of rate-limit retries got reached.' });
}
returnData.push({success: true});

View File

@@ -21,6 +21,12 @@
}
]
},
"alias": [
"Shell",
"Command",
"OS",
"Bash"
],
"subcategories": {
"Core Nodes": [
"Helpers"

View File

@@ -13,6 +13,9 @@
}
]
},
"alias": [
"n8n"
],
"subcategories": {
"Core Nodes": [
"Helpers"

View File

@@ -11,7 +11,7 @@ import {
NodeApiError,
} from 'n8n-workflow';
import * as uuid from 'uuid/v4';
import { v4 as uuid } from 'uuid';
import {
snakeCase,

View File

@@ -64,6 +64,11 @@
"icon": "⏲",
"url": "https://n8n.io/blog/creating-triggers-for-n8n-workflows-using-polling/"
},
{
"label": "How to build a low-code, self-hosted URL shortener in 3 steps",
"icon": "🔗",
"url": "https://n8n.io/blog/how-to-build-a-low-code-self-hosted-url-shortener/"
},
{
"label": "Build your own virtual assistant with n8n: A step by step guide",
"icon": "👦",
@@ -101,6 +106,13 @@
}
]
},
"alias": [
"Code",
"Javascript",
"Custom Code",
"Script",
"cpde"
],
"subcategories": {
"Core Nodes": [
"Data Transformation"

View File

@@ -70,16 +70,15 @@ export async function ghostApiRequestAllItems(this: IHookFunctions | IExecuteFun
let responseData;
query.limit = 20;
let uri: string | undefined;
query.limit = 50;
query.page = 1;
do {
responseData = await ghostApiRequest.call(this, method, endpoint, body, query, uri);
uri = responseData.meta.pagination.next;
responseData = await ghostApiRequest.call(this, method, endpoint, body, query);
query.page = responseData.meta.pagination.next;
returnData.push.apply(returnData, responseData[propertyName]);
} while (
responseData.meta.pagination.next !== null
query.page !== null
);
return returnData;
}

View File

@@ -0,0 +1,24 @@
{
"node": "n8n-nodes-base.git",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": [
"Core Nodes",
"Development"
],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/credentials/git"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.git/"
}
]
},
"subcategories": [
"Helpers"
]
}

View File

@@ -0,0 +1,437 @@
import { IExecuteFunctions } from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import {
addConfigFields,
addFields,
cloneFields,
commitFields,
logFields,
pushFields,
tagFields,
} from './descriptions';
import simpleGit, {
LogOptions,
SimpleGit,
SimpleGitOptions,
} from 'simple-git';
import {
access,
mkdir,
} from 'fs/promises';
import { URL } from 'url';
export class Git implements INodeType {
description: INodeTypeDescription = {
displayName: 'Git',
name: 'git',
icon: 'file:git.svg',
group: ['transform'],
version: 1,
description: 'Control git.',
defaults: {
name: 'Git',
color: '#808080',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'gitPassword',
required: true,
displayOptions: {
show: {
authentication: [
'gitPassword',
],
},
},
},
],
properties: [
{
displayName: 'Authentication',
name: 'authentication',
type: 'options',
options: [
{
name: 'Authenticate',
value: 'gitPassword',
},
{
name: 'None',
value: 'none',
},
],
displayOptions: {
show: {
operation: [
'clone',
'push',
],
},
},
default: 'none',
description: 'The way to authenticate.',
},
{
displayName: 'Operation',
name: 'operation',
type: 'options',
default: 'log',
description: 'Operation to perform',
options: [
{
name: 'Add',
value: 'add',
description: 'Add a file or folder to commit',
},
{
name: 'Add Config',
value: 'addConfig',
description: 'Add configuration property',
},
{
name: 'Clone',
value: 'clone',
description: 'Clone a repository',
},
{
name: 'Commit',
value: 'commit',
description: 'Commit files or folders to git',
},
{
name: 'Fetch',
value: 'fetch',
description: 'Fetch from remote repository',
},
{
name: 'List Config',
value: 'listConfig',
description: 'Return current configuration',
},
{
name: 'Log',
value: 'log',
description: 'Return git commit history',
},
{
name: 'Pull',
value: 'pull',
description: 'Pull from remote repository',
},
{
name: 'Push',
value: 'push',
description: 'Push to remote repository',
},
{
name: 'Push Tags',
value: 'pushTags',
description: 'Push Tags to remote repository',
},
{
name: 'Status',
value: 'status',
description: 'Return status of current repository',
},
{
name: 'Tag',
value: 'tag',
description: 'Create a new tag',
},
{
name: 'User Setup',
value: 'userSetup',
description: 'Set the user',
},
],
},
{
displayName: 'Repository Path',
name: 'repositoryPath',
type: 'string',
displayOptions: {
hide: {
operation: [
'clone',
],
},
},
default: '',
placeholder: '/tmp/repository',
required: true,
description: 'Local path of the git repository to operate on.',
},
{
displayName: 'New Repository Path',
name: 'repositoryPath',
type: 'string',
displayOptions: {
show: {
operation: [
'clone',
],
},
},
default: '',
placeholder: '/tmp/repository',
required: true,
description: 'Local path to which the git repository should be cloned into.',
},
...addFields,
...addConfigFields,
...cloneFields,
...commitFields,
...logFields,
...pushFields,
...tagFields,
// ...userSetupFields,
],
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const prepareRepository = (repositoryPath: string): string => {
const authentication = this.getNodeParameter('authentication', 0) as string;
if (authentication === 'gitPassword') {
const gitCredentials = this.getCredentials('gitPassword') as IDataObject;
const url = new URL(repositoryPath);
url.username = gitCredentials.username as string;
url.password = gitCredentials.password as string;
return url.toString();
}
return repositoryPath;
};
const operation = this.getNodeParameter('operation', 0) as string;
let item: INodeExecutionData;
const returnItems: INodeExecutionData[] = [];
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
item = items[itemIndex];
const repositoryPath = this.getNodeParameter('repositoryPath', itemIndex, '') as string;
const options = this.getNodeParameter('options', itemIndex, {}) as IDataObject;
if (operation === 'clone') {
// Create repository folder if it does not exist
try {
await access(repositoryPath);
} catch (error) {
await mkdir(repositoryPath);
}
}
const gitOptions: Partial<SimpleGitOptions> = {
baseDir: repositoryPath,
};
const git: SimpleGit = simpleGit(gitOptions)
// Tell git not to ask for any information via the terminal like for
// example the username. As nobody will be able to answer it would
// n8n keep on waiting forever.
.env('GIT_TERMINAL_PROMPT', '0');
if (operation === 'add') {
// ----------------------------------
// add
// ----------------------------------
const pathsToAdd = this.getNodeParameter('pathsToAdd', itemIndex, '') as string;
await git.add(pathsToAdd.split(','));
returnItems.push({ json: { success: true } });
} else if (operation === 'addConfig') {
// ----------------------------------
// addConfig
// ----------------------------------
const key = this.getNodeParameter('key', itemIndex, '') as string;
const value = this.getNodeParameter('value', itemIndex, '') as string;
let append = false;
if (options.mode === 'append') {
append = true;
}
await git.addConfig(key, value, append);
returnItems.push({ json: { success: true } });
} else if (operation === 'clone') {
// ----------------------------------
// clone
// ----------------------------------
let sourceRepository = this.getNodeParameter('sourceRepository', itemIndex, '') as string;
sourceRepository = prepareRepository(sourceRepository);
await git.clone(sourceRepository, '.');
returnItems.push({ json: { success: true } });
} else if (operation === 'commit') {
// ----------------------------------
// commit
// ----------------------------------
const message = this.getNodeParameter('message', itemIndex, '') as string;
let pathsToAdd: string[] | undefined = undefined;
if (options.files !== undefined) {
pathsToAdd = (options.pathsToAdd as string).split(',');
}
await git.commit(message, pathsToAdd);
returnItems.push({ json: { success: true } });
} else if (operation === 'fetch') {
// ----------------------------------
// fetch
// ----------------------------------
await git.fetch();
returnItems.push({ json: { success: true } });
} else if (operation === 'log') {
// ----------------------------------
// log
// ----------------------------------
const logOptions: LogOptions = {};
const returnAll = this.getNodeParameter('returnAll', itemIndex, false) as boolean;
if (returnAll === false) {
logOptions.maxCount = this.getNodeParameter('limit', itemIndex, 100) as number;
}
if (options.file) {
logOptions.file = options.file as string;
}
const log = await git.log(logOptions);
// @ts-ignore
returnItems.push(...this.helpers.returnJsonArray(log.all));
} else if (operation === 'pull') {
// ----------------------------------
// pull
// ----------------------------------
await git.pull();
returnItems.push({ json: { success: true } });
} else if (operation === 'push') {
// ----------------------------------
// push
// ----------------------------------
if (options.repository) {
const targetRepository = prepareRepository(options.targetRepository as string);
await git.push(targetRepository);
} else {
const authentication = this.getNodeParameter('authentication', 0) as string;
if (authentication === 'gitPassword') {
// Try to get remote repository path from git repository itself to add
// authentication data
const config = await git.listConfig();
let targetRepository;
for (const fileName of Object.keys(config.values)) {
if (config.values[fileName]['remote.origin.url']) {
targetRepository = config.values[fileName]['remote.origin.url'];
break;
}
}
targetRepository = prepareRepository(targetRepository as string);
await git.push(targetRepository);
} else {
await git.push();
}
}
returnItems.push({ json: { success: true } });
} else if (operation === 'pushTags') {
// ----------------------------------
// pushTags
// ----------------------------------
await git.pushTags();
returnItems.push({ json: { success: true } });
} else if (operation === 'listConfig') {
// ----------------------------------
// listConfig
// ----------------------------------
const config = await git.listConfig();
const data = [];
for (const fileName of Object.keys(config.values)) {
data.push({
_file: fileName,
...config.values[fileName],
});
}
// @ts-ignore
returnItems.push(...this.helpers.returnJsonArray(data));
} else if (operation === 'status') {
// ----------------------------------
// status
// ----------------------------------
const status = await git.status();
// @ts-ignore
returnItems.push(...this.helpers.returnJsonArray([status]));
} else if (operation === 'tag') {
// ----------------------------------
// tag
// ----------------------------------
const name = this.getNodeParameter('name', itemIndex, '') as string;
await git.addTag(name);
returnItems.push({ json: { success: true } });
}
} catch (error) {
if (this.continueOnFail()) {
returnItems.push({ json: { error: error.toString() } });
continue;
}
throw error;
}
}
return this.prepareOutputData(returnItems);
}
}

View File

@@ -0,0 +1,71 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const addConfigFields = [
{
displayName: 'Key',
name: 'key',
type: 'string',
displayOptions: {
show: {
operation: [
'addConfig',
],
},
},
default: '',
placeholder: 'user.email',
description: 'Name of the key to set.',
required: true,
},
{
displayName: 'Value',
name: 'value',
type: 'string',
displayOptions: {
show: {
operation: [
'addConfig',
],
},
},
default: '',
placeholder: 'name@example.com',
description: 'Value of the key to set.',
required: true,
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
operation: [
'addConfig',
],
},
},
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Mode',
name: 'mode',
type: 'options',
options: [
{
name: 'Append',
value: 'append',
},
{
name: 'Set',
value: 'set',
},
],
default: 'set',
description: 'Append setting rather than set it in the local config.',
},
],
},
] as INodeProperties[];

View File

@@ -0,0 +1,22 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const addFields = [
{
displayName: 'Paths to Add',
name: 'pathsToAdd',
type: 'string',
displayOptions: {
show: {
operation: [
'add',
],
},
},
default: '',
placeholder: 'README.md',
description: 'Comma separated list of paths (absolute or relative to Repository Path) of files or folders to add.',
required: true,
},
] as INodeProperties[];

View File

@@ -0,0 +1,22 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const cloneFields = [
{
displayName: 'Source Repository',
name: 'sourceRepository',
type: 'string',
displayOptions: {
show: {
operation: [
'clone',
],
},
},
default: '',
placeholder: 'https://github.com/n8n-io/n8n',
description: 'The URL or path of the repository to clone.',
required: true,
},
] as INodeProperties[];

View File

@@ -0,0 +1,45 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const commitFields = [
{
displayName: 'Message',
name: 'message',
type: 'string',
displayOptions: {
show: {
operation: [
'commit',
],
},
},
default: '',
description: 'The commit message to use.',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
operation: [
'commit',
],
},
},
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Paths to Add',
name: 'pathsToAdd',
type: 'string',
default: '',
placeholder: '/data/file1.json',
description: `Comma separated list of paths (absolute or relative to Repository Path) of<br />
files or folders to commit. If not set will all "added" files and folders be committed.`,
},
],
},
] as INodeProperties[];

View File

@@ -0,0 +1,64 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const logFields = [
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: {
operation: [
'log',
],
},
},
default: false,
description: 'If all results should be returned or only up to a given limit.',
},
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
operation: [
'log',
],
returnAll: [
false,
],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 100,
description: 'How many results to return.',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
operation: [
'log',
],
},
},
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'File',
name: 'file',
type: 'string',
default: 'README.md',
description: 'The path (absolute or relative to Repository Path) of file or folder to get the history of.',
},
],
},
] as INodeProperties[];

View File

@@ -0,0 +1,30 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const pushFields = [
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
operation: [
'push',
],
},
},
placeholder: 'Add Option',
default: {},
options: [
{
displayName: 'Target Repository',
name: 'targetRepository',
type: 'string',
default: '',
placeholder: 'https://github.com/n8n-io/n8n',
description: 'The URL or path of the repository to push to.',
},
],
},
] as INodeProperties[];

View File

@@ -0,0 +1,21 @@
import {
INodeProperties,
} from 'n8n-workflow';
export const tagFields = [
{
displayName: 'Name',
name: 'name',
type: 'string',
displayOptions: {
show: {
operation: [
'tag',
],
},
},
default: '',
description: 'The name of the tag to create.',
required: true,
},
] as INodeProperties[];

View File

@@ -0,0 +1,7 @@
export * from './AddDescription';
export * from './AddConfigDescription';
export * from './CloneDescription';
export * from './CommitDescription';
export * from './LogDescription';
export * from './PushDescription';
export * from './TagDescription';

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="97" height="97"><path fill="#F05133" d="M92.71 44.408L52.591 4.291a5.918 5.918 0 00-8.369 0l-8.33 8.332L46.459 23.19a7.022 7.022 0 017.229 1.685 7.03 7.03 0 011.67 7.275l10.186 10.185a7.028 7.028 0 017.275 1.671 7.043 7.043 0 01-9.961 9.958 7.043 7.043 0 01-1.531-7.658l-9.5-9.499v24.997a7.042 7.042 0 11-8.096 11.291 7.042 7.042 0 012.307-11.496v-25.23a7.041 7.041 0 01-3.823-9.235L31.798 16.715 4.288 44.222a5.92 5.92 0 000 8.371l40.121 40.118a5.918 5.918 0 008.369 0L92.71 52.779a5.92 5.92 0 000-8.371z"/></svg>

After

Width:  |  Height:  |  Size: 561 B

View File

@@ -70,6 +70,10 @@ export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOp
export function simplify(responseData: any | [any]) { // tslint:disable-line:no-any
const response = [];
for (const { columnHeader: { dimensions }, data: { rows } } of responseData) {
if (rows === undefined) {
// Do not error if there is no data
continue;
}
for (const row of rows) {
const data: IDataObject = {};
if (dimensions) {

View File

@@ -112,6 +112,15 @@ export class GoogleAnalytics implements INodeType {
});
}
}
returnData.sort((a, b) => {
const aName= a.name.toLowerCase();
const bName= b.name.toLowerCase();
if (aName < bName) { return -1; }
if (aName > bName) { return 1; }
return 0;
});
return returnData;
},
// Get all the views to display them to user so that he can
@@ -203,6 +212,14 @@ export class GoogleAnalytics implements INodeType {
body.dimensions = dimensions;
}
}
if (additionalFields.dimensionFiltersUi) {
const dimensionFilters = (additionalFields.dimensionFiltersUi as IDataObject).filterValues as IDataObject[];
if (dimensionFilters) {
dimensionFilters.forEach(filter => filter.expressions = [filter.expressions]);
body.dimensionFilterClauses = { filters: dimensionFilters };
}
}
if (additionalFields.includeEmptyRows) {
Object.assign(body, { includeEmptyRows: additionalFields.includeEmptyRows });
}

View File

@@ -1,6 +1,9 @@
export interface IData {
viewId: string;
dimensions?: IDimension[];
dimensionFilterClauses?: {
filters: IDimensionFilter[];
};
pageSize?: number;
metrics?: IMetric[];
}
@@ -10,6 +13,11 @@ export interface IDimension {
histogramBuckets?: string[];
}
export interface IDimensionFilter {
dimensionName?: string;
operator?: string;
expressions?: string[];
}
export interface IMetric {
expression?: string;
alias?: string;

View File

@@ -183,6 +183,85 @@ export const reportFields = [
},
],
},
{
displayName: 'Dimension Filters',
name: 'dimensionFiltersUi',
type: 'fixedCollection',
default: {},
typeOptions: {
multipleValues: true,
},
placeholder: 'Add Dimension Filter',
description: 'Dimension Filters in the request',
options: [
{
displayName: 'Filters',
name: 'filterValues',
values: [
{
displayName: 'Dimension Name',
name: 'dimensionName',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getDimensions',
},
default: '',
description: 'Name of the dimension to filter by.',
},
// https://developers.google.com/analytics/devguides/reporting/core/v4/rest/v4/reports/batchGet#Operator
{
displayName: 'Operator',
name: 'operator',
type: 'options',
default: 'EXACT',
description: 'Operator to use in combination with value.',
options: [
{
name: 'Begins With',
value: 'BEGINS_WITH',
},
{
name: 'Ends With',
value: 'ENDS_WITH',
},
{
name: 'Exact',
value: 'EXACT',
},
{
name: 'Greater Than (number)',
value: 'NUMERIC_GREATER_THAN',
},
{
name: 'Partial',
value: 'PARTIAL',
},
{
name: 'Regular Expression',
value: 'REGEXP',
},
{
name: 'Equal (number)',
value: 'NUMERIC_EQUAL',
},
{
name: 'Less Than (number)',
value: 'NUMERIC_LESS_THAN',
},
],
},
{
displayName: 'Value',
name: 'expressions',
type: 'string',
default: '',
placeholder: 'ga:newUsers',
description: `String or <a href="https://support.google.com/analytics/answer/1034324?hl=en" target="_blank">regular expression</a> to match against.`,
},
],
},
],
},
{
displayName: 'Hide Totals',
name: 'hideTotals',

View File

@@ -22,7 +22,7 @@ import {
recordOperations,
} from './RecordDescription';
import * as uuid from 'uuid';
import { v4 as uuid } from 'uuid';
export class GoogleBigQuery implements INodeType {
description: INodeTypeDescription = {

View File

@@ -53,6 +53,10 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
return await this.helpers.requestOAuth2.call(this, 'googleBooksOAuth2Api', options);
}
} catch (error) {
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
error.statusCode = '401';
}
throw new NodeApiError(this.getNode(), error);
}
}

View File

@@ -34,7 +34,7 @@ import {
import * as moment from 'moment-timezone';
import * as uuid from 'uuid/v4';
import { v4 as uuid } from 'uuid';
export class GoogleCalendar implements INodeType {
description: INodeTypeDescription = {

View File

@@ -22,6 +22,11 @@
"icon": "⚙️",
"url": "https://n8n.io/blog/automate-your-data-processing-pipeline-in-9-steps-with-n8n/"
},
{
"label": "5 tasks you can automate with the new Notion API ",
"icon": "⚡️",
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
},
{
"label": "15 Google apps you can combine and automate to increase productivity",
"icon": "💡",

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,142 @@
import {
OptionsWithUri,
} from 'request';
import {
IExecuteFunctions,
ILoadOptionsFunctions,
} from 'n8n-core';
import {
IDataObject,
NodeApiError,
NodeOperationError,
} from 'n8n-workflow';
import * as moment from 'moment-timezone';
import * as jwt from 'jsonwebtoken';
export async function googleApiRequest(
this: IExecuteFunctions | ILoadOptionsFunctions,
method: string,
endpoint: string,
body: IDataObject = {},
qs?: IDataObject,
uri?: string,
) {
const authenticationMethod = this.getNodeParameter('authentication', 0, 'serviceAccount') as string;
const options: OptionsWithUri = {
headers: {
'Content-Type': 'application/json',
},
method,
body,
qs,
uri: uri || `https://docs.googleapis.com/v1${endpoint}`,
json: true,
};
if (!Object.keys(body).length) {
delete options.body;
}
try {
if (authenticationMethod === 'serviceAccount') {
const credentials = this.getCredentials('googleApi');
if (credentials === undefined) {
throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
}
const { access_token } = await getAccessToken.call(this, credentials as IDataObject);
options.headers!.Authorization = `Bearer ${access_token}`;
return await this.helpers.request!(options);
} else {
//@ts-ignore
return await this.helpers.requestOAuth2.call(this, 'googleDocsOAuth2Api', options);
}
} catch (error) {
throw new NodeApiError(this.getNode(), error);
}
}
export async function googleApiRequestAllItems(this: IExecuteFunctions | ILoadOptionsFunctions, propertyName: string, method: string, endpoint: string, body: IDataObject = {}, qs?: IDataObject, uri?: string): Promise<any> { // tslint:disable-line:no-any
const returnData: IDataObject[] = [];
let responseData;
const query: IDataObject = { ...qs };
query.maxResults = 100;
query.pageSize = 100;
do {
responseData = await googleApiRequest.call(this, method, endpoint, body, query, uri);
query.pageToken = responseData['nextPageToken'];
returnData.push.apply(returnData, responseData[propertyName]);
} while (
responseData['nextPageToken'] !== undefined &&
responseData['nextPageToken'] !== ''
);
return returnData;
}
function getAccessToken(this: IExecuteFunctions | ILoadOptionsFunctions, credentials: IDataObject): Promise<IDataObject> {
//https://developers.google.com/identity/protocols/oauth2/service-account#httprest
const scopes = [
'https://www.googleapis.com/auth/documents',
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/drive.file',
];
const now = moment().unix();
const signature = jwt.sign(
{
'iss': credentials.email as string,
'sub': credentials.delegatedEmail || credentials.email as string,
'scope': scopes.join(' '),
'aud': `https://oauth2.googleapis.com/token`,
'iat': now,
'exp': now + 3600,
},
credentials.privateKey as string,
{
algorithm: 'RS256',
header: {
'kid': credentials.privateKey as string,
'typ': 'JWT',
'alg': 'RS256',
},
},
);
const options: OptionsWithUri = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
method: 'POST',
form: {
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
assertion: signature,
},
uri: 'https://oauth2.googleapis.com/token',
json: true,
};
return this.helpers.request!(options);
}
export const hasKeys = (obj = {}) => Object.keys(obj).length > 0;
export const extractID = (url: string) => {
const regex = new RegExp('https://docs.google.com/document/d/([a-zA-Z0-9-_]+)/');
const results = regex.exec(url);
return results ? results[1] : undefined;
};
export const upperFirst = (str: string) => {
return str[0].toUpperCase() + str.substr(1);
};

View File

@@ -0,0 +1,20 @@
{
"node": "n8n-nodes-base.googleDocs",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": [
"Miscellaneous"
],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.n8n.io/credentials/google"
}
],
"primaryDocumentation": [
{
"url": "https://docs.n8n.io/nodes/n8n-nodes-base.googleDocs/"
}
]
}
}

View File

@@ -0,0 +1,461 @@
import {
IExecuteFunctions,
ILoadOptionsFunctions,
} from 'n8n-core';
import {
IDataObject,
INodeExecutionData,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
NodeApiError,
} from 'n8n-workflow';
import {
extractID,
googleApiRequest,
googleApiRequestAllItems,
hasKeys,
upperFirst,
} from './GenericFunctions';
import {
documentFields,
documentOperations,
} from './DocumentDescription';
import {
IUpdateBody,
IUpdateFields,
} from './interfaces';
export class GoogleDocs implements INodeType {
description: INodeTypeDescription = {
displayName: 'Google Docs',
name: 'googleDocs',
icon: 'file:googleDocs.svg',
group: ['input'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume Google Docs API.',
defaults: {
name: 'Google Docs',
color: '#1a73e8',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'googleApi',
required: true,
displayOptions: {
show: {
authentication: [
'serviceAccount',
],
},
},
},
{
name: 'googleDocsOAuth2Api',
required: true,
displayOptions: {
show: {
authentication: [
'oAuth2',
],
},
},
},
],
properties: [
{
displayName: 'Authentication',
name: 'authentication',
type: 'options',
options: [
{
name: 'Service Account',
value: 'serviceAccount',
},
{
name: 'OAuth2',
value: 'oAuth2',
},
],
default: 'serviceAccount',
},
{
displayName: 'Resource',
name: 'resource',
type: 'options',
options: [
{
name: 'Document',
value: 'document',
},
],
default: 'document',
description: 'The resource to operate on.',
},
...documentOperations,
...documentFields,
],
};
methods = {
loadOptions: {
// Get all the drives to display them to user so that he can
// select them easily
async getDrives(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [
{
name: 'My Drive',
value: 'myDrive',
},
{
name: 'Shared with me',
value: 'sharedWithMe',
},
];
let drives;
try {
drives = await googleApiRequestAllItems.call(this, 'drives', 'GET', '', {}, {}, 'https://www.googleapis.com/drive/v3/drives');
} catch (error) {
throw new NodeApiError(this.getNode(), error, { message: 'Error in loading Drives' });
}
for (const drive of drives) {
returnData.push({
name: drive.name as string,
value: drive.id as string,
});
}
return returnData;
},
async getFolders(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const returnData: INodePropertyOptions[] = [
{
name: '/',
value: 'default',
},
];
const driveId = this.getNodeParameter('driveId');
const qs = {
q: `mimeType = \'application/vnd.google-apps.folder\' ${driveId === 'sharedWithMe' ? 'and sharedWithMe = true' : ' and \'root\' in parents'}`,
...(driveId && driveId !== 'myDrive' && driveId !== 'sharedWithMe') ? { driveId } : {},
};
let folders;
try {
folders = await googleApiRequestAllItems.call(this, 'files', 'GET', '', {}, qs, 'https://www.googleapis.com/drive/v3/files');
} catch (error) {
throw new NodeApiError(this.getNode(), error, { message: 'Error in loading Folders' });
}
for (const folder of folders) {
returnData.push({
name: folder.name as string,
value: folder.id as string,
});
}
return returnData;
},
},
};
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
const returnData: IDataObject[] = [];
const length = items.length;
let responseData;
const resource = this.getNodeParameter('resource', 0) as string;
const operation = this.getNodeParameter('operation', 0) as string;
for (let i = 0; i < length; i++) {
try {
if (resource === 'document') {
if (operation === 'create') {
// https://developers.google.com/docs/api/reference/rest/v1/documents/create
const folderId = this.getNodeParameter('folderId', i) as string;
const body: IDataObject = {
name: this.getNodeParameter('title', i) as string,
mimeType: 'application/vnd.google-apps.document',
...(folderId && folderId !== 'default') ? { parents: [folderId] } : {},
};
responseData = await googleApiRequest.call(this, 'POST', '', body, {}, 'https://www.googleapis.com/drive/v3/files');
} else if (operation === 'get') {
// https://developers.google.com/docs/api/reference/rest/v1/documents/get
const documentURL = this.getNodeParameter('documentURL', i) as string;
const simple = this.getNodeParameter('simple', i) as boolean;
let documentId = extractID(documentURL);
if (!documentId) {
documentId = documentURL;
}
responseData = await googleApiRequest.call(this, 'GET', `/documents/${documentId}`);
if (simple) {
const content = (responseData.body.content as IDataObject[])
.reduce((arr: string[], contentItem) => {
if (contentItem && contentItem.paragraph) {
const texts = ((contentItem.paragraph as IDataObject).elements as IDataObject[])
.map(element => {
if (element && element.textRun) {
return (element.textRun as IDataObject).content as string;
}
}) as string[];
arr = [...arr, ...texts];
}
return arr;
}, [])
.join('');
responseData = {
documentId,
content,
};
}
} else if (operation === 'update') {
// https://developers.google.com/docs/api/reference/rest/v1/documents/batchUpdate
const documentURL = this.getNodeParameter('documentURL', i) as string;
let documentId = extractID(documentURL);
const simple = this.getNodeParameter('simple', i) as boolean;
const actionsUi = this.getNodeParameter('actionsUi', i) as {
actionFields: IDataObject[]
};
const { writeControlObject } = this.getNodeParameter('updateFields', i) as IUpdateFields;
if (!documentId) {
documentId = documentURL;
}
const body = {
requests: [],
} as IUpdateBody;
if (hasKeys(writeControlObject)) {
const { control, value } = writeControlObject;
body.writeControl = {
[control]: value,
};
}
if (actionsUi) {
let requestBody: IDataObject;
actionsUi.actionFields.forEach(actionField => {
const { action, object } = actionField;
if (object === 'positionedObject') {
if (action === 'delete') {
requestBody = {
objectId: actionField.objectId,
};
}
} else if (object === 'pageBreak') {
if (action === 'insert') {
const { insertSegment, segmentId, locationChoice, index } = actionField;
requestBody = {
[locationChoice as string]: {
segmentId: (insertSegment !== 'body') ? segmentId : '',
...(locationChoice === 'location') ? { index } : {},
},
};
}
} else if (object === 'table') {
if (action === 'insert') {
const { rows, columns, insertSegment, locationChoice, segmentId, index } = actionField;
requestBody = {
rows,
columns,
[locationChoice as string]: {
segmentId: (insertSegment !== 'body') ? segmentId : '',
...(locationChoice === 'location') ? { index } : {},
},
};
}
} else if (object === 'footer') {
if (action === 'create') {
const { insertSegment, locationChoice, segmentId, index } = actionField;
requestBody = {
type: 'DEFAULT',
sectionBreakLocation: {
segmentId: (insertSegment !== 'body') ? segmentId : '',
...(locationChoice === 'location') ? { index } : {},
},
};
} else if (action === 'delete') {
requestBody = {
footerId: actionField.footerId,
};
}
} else if (object === 'header') {
if (action === 'create') {
const { insertSegment, locationChoice, segmentId, index } = actionField;
requestBody = {
type: 'DEFAULT',
sectionBreakLocation: {
segmentId: (insertSegment !== 'body') ? segmentId : '',
...(locationChoice === 'location') ? { index } : {},
},
};
} else if (action === 'delete') {
requestBody = {
headerId: actionField.headerId,
};
}
} else if (object === 'tableColumn') {
if (action === 'insert') {
const { insertPosition, rowIndex, columnIndex, insertSegment, segmentId, index } = actionField;
requestBody = {
insertRight: insertPosition,
tableCellLocation: {
rowIndex,
columnIndex,
tableStartLocation: { segmentId: (insertSegment !== 'body') ? segmentId : '', index, },
},
};
} else if (action === 'delete') {
const { rowIndex, columnIndex, insertSegment, segmentId, index } = actionField;
requestBody = {
tableCellLocation: {
rowIndex,
columnIndex,
tableStartLocation: { segmentId: (insertSegment !== 'body') ? segmentId : '', index, },
},
};
}
} else if (object === 'tableRow') {
if (action === 'insert') {
const { insertPosition, rowIndex, columnIndex, insertSegment, segmentId, index } = actionField;
requestBody = {
insertBelow: insertPosition,
tableCellLocation: {
rowIndex,
columnIndex,
tableStartLocation: { segmentId: (insertSegment !== 'body') ? segmentId : '', index, },
},
};
} else if (action === 'delete') {
const { rowIndex, columnIndex, insertSegment, segmentId, index } = actionField;
requestBody = {
tableCellLocation: {
rowIndex,
columnIndex,
tableStartLocation: { segmentId: (insertSegment !== 'body') ? segmentId : '', index, },
},
};
}
} else if (object === 'text') {
if (action === 'insert') {
const { text, locationChoice, insertSegment, segmentId, index } = actionField;
requestBody = {
text,
[locationChoice as string]: {
segmentId: (insertSegment !== 'body') ? segmentId : '',
...(locationChoice === 'location') ? { index } : {},
},
};
} else if (action === 'replaceAll') {
const { text, replaceText, matchCase } = actionField;
requestBody = {
replaceText,
containsText: { text, matchCase },
};
}
} else if (object === 'paragraphBullets') {
if (action === 'create') {
const { bulletPreset, startIndex, insertSegment, segmentId, endIndex } = actionField;
requestBody = {
bulletPreset,
range: { segmentId: (insertSegment !== 'body') ? segmentId : '', startIndex, endIndex },
};
} else if (action === 'delete') {
const { startIndex, insertSegment, segmentId, endIndex } = actionField;
requestBody = {
range: { segmentId: (insertSegment !== 'body') ? segmentId : '', startIndex, endIndex },
};
}
} else if (object === 'namedRange') {
if (action === 'create') {
const { name, insertSegment, segmentId, startIndex, endIndex } = actionField;
requestBody = {
name,
range: { segmentId: (insertSegment !== 'body') ? segmentId : '', startIndex, endIndex },
};
} else if (action === 'delete') {
const { namedRangeReference, value } = actionField;
requestBody = {
[namedRangeReference as string]: value,
};
}
}
body.requests.push({
[`${action}${upperFirst(object as string)}`]: requestBody,
});
});
}
responseData = await googleApiRequest.call(this, 'POST', `/documents/${documentId}:batchUpdate`, body);
if (simple === true) {
if (Object.keys(responseData.replies[0]).length !== 0) {
const key = Object.keys(responseData.replies[0])[0];
responseData = responseData.replies[0][key];
} else {
responseData = {};
}
}
responseData.documentId = documentId;
}
}
} catch (error) {
if (this.continueOnFail()) {
returnData.push({ error: error.message });
continue;
}
throw error;
}
Array.isArray(responseData)
? returnData.push(...responseData)
: returnData.push(responseData);
}
return [this.helpers.returnJsonArray(returnData)];
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-18 0 90 80" fill="#fff" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round"><use xlink:href="#A" x=".5" y=".5"/><symbol id="A" overflow="visible"><g stroke="none"><path fill="#548df6" d="M36 0l22 22v53a4.99 4.99 0 0 1-5 5H5a4.99 4.99 0 0 1-5-5V5a4.99 4.99 0 0 1 5-5z"/><path d="M14 40h30v3H14zm0 7h30v3H14zm0 8h30v3H14zm0 7h21v3H14z"/><path fill="#abd0fb" d="M36 0l22 22H41c-2.77 0-5-2.48-5-5.25z"/><path fill="#3e5bb9" d="M40.75 22L58 29.125V22z"/></g></symbol></svg>

After

Width:  |  Height:  |  Size: 591 B

View File

@@ -0,0 +1,13 @@
import { IDataObject } from 'n8n-workflow';
export interface IUpdateBody extends IDataObject {
requests: IDataObject[];
writeControl?: { [key: string]: string };
}
export interface IUpdateFields {
writeControlObject: {
control: string,
value: string,
};
}

View File

@@ -51,6 +51,10 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
return await this.helpers.requestOAuth2.call(this, 'googleDriveOAuth2Api', options);
}
} catch (error) {
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
error.statusCode = '401';
}
throw new NodeApiError(this.getNode(), error);
}
}

View File

@@ -16,7 +16,7 @@ import {
googleApiRequestAllItems,
} from './GenericFunctions';
import uuid = require('uuid');
import { v4 as uuid } from 'uuid';
export class GoogleDrive implements INodeType {
description: INodeTypeDescription = {
@@ -741,6 +741,7 @@ export class GoogleDrive implements INodeType {
placeholder: '',
description: 'Name of the binary property which contains<br />the data for the file to be uploaded.',
},
// ----------------------------------
// file:update
// ----------------------------------
@@ -1898,6 +1899,91 @@ export class GoogleDrive implements INodeType {
},
],
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
placeholder: 'Add Option',
default: {},
displayOptions: {
show: {
operation: [
'upload',
],
resource: [
'file',
],
},
},
options: [
{
displayName: 'APP Properties',
name: 'appPropertiesUi',
placeholder: 'Add Property',
type: 'fixedCollection',
default: '',
typeOptions: {
multipleValues: true,
},
description: 'A collection of arbitrary key-value pairs which are private to the requesting app',
options: [
{
name: 'appPropertyValues',
displayName: 'APP Property',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
description: 'Name of the key to add.',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Value to set for the key.',
},
],
},
],
},
{
displayName: 'Properties',
name: 'propertiesUi',
placeholder: 'Add Property',
type: 'fixedCollection',
default: '',
typeOptions: {
multipleValues: true,
},
description: 'A collection of arbitrary key-value pairs which are visible to all apps.',
options: [
{
name: 'propertyValues',
displayName: 'Property',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
description: 'Name of the key to add.',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
description: 'Value to set for the key.',
},
],
},
],
},
],
},
],
};
@@ -2228,6 +2314,18 @@ export class GoogleDrive implements INodeType {
originalFilename,
};
const properties = this.getNodeParameter('options.propertiesUi.propertyValues', i, []) as IDataObject[];
if (properties.length) {
Object.assign(body, { properties: properties.reduce((obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), {}) } );
}
const appProperties = this.getNodeParameter('options.appPropertiesUi.appPropertyValues', i, []) as IDataObject[];
if (properties.length) {
Object.assign(body, { appProperties: appProperties.reduce((obj, value) => Object.assign(obj, { [`${value.key}`]: value.value }), {}) });
}
qs = {
addParents: parents.join(','),
// When set to true shared drives can be used.

View File

@@ -23,5 +23,8 @@
"url": "https://n8n.io/blog/automate-google-apps-for-productivity/"
}
]
}
},
"alias": [
"Workspaces"
]
}

View File

@@ -74,6 +74,10 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
}
} catch (error) {
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
error.statusCode = '401';
}
throw new NodeApiError(this.getNode(), error);
}
}

View File

@@ -53,6 +53,10 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF
return await this.helpers.requestOAuth2.call(this, 'googleSheetsOAuth2Api', options);
}
} catch (error) {
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
error.statusCode = '401';
}
throw new NodeApiError(this.getNode(), error);
}
}
@@ -138,4 +142,4 @@ export function hexToRgb(hex: string) {
green: parseInt(result[2], 16),
blue: parseInt(result[3], 16),
} : null;
}
}

View File

@@ -79,5 +79,10 @@
"url": "https://n8n.io/blog/sending-automated-congratulations-with-google-sheets-twilio-and-n8n/"
}
]
}
},
"alias": [
"CSV",
"Spreadsheet",
"GS"
]
}

View File

@@ -54,6 +54,10 @@ export async function googleApiRequest(
return await this.helpers.requestOAuth2!.call(this, 'googleSlidesOAuth2Api', options);
}
} catch (error) {
if (error.code === 'ERR_OSSL_PEM_NO_START_LINE') {
error.statusCode = '401';
}
throw new NodeApiError(this.getNode(), error);
}
}

View File

@@ -13,6 +13,11 @@
}
],
"generic": [
{
"label": "What are APIs and how to use them with no code",
"icon": " 🪢",
"url": "https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/"
},
{
"label": "How to automatically give kudos to contributors with GitHub, Slack, and n8n",
"icon": "👏",

View File

@@ -17,6 +17,11 @@
"icon": "🧬",
"url": "https://n8n.io/blog/why-business-process-automation-with-n8n-can-change-your-daily-life/"
},
{
"label": "5 tasks you can automate with the new Notion API ",
"icon": "⚡️",
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
},
{
"label": "How to use the HTTP Request Node - The Swiss Army Knife for Workflow Automation",
"icon": "🧰",

View File

@@ -43,6 +43,16 @@
"icon": "🛳",
"url": "https://n8n.io/blog/running-n8n-on-ships-an-interview-with-maranics/"
},
{
"label": "What are APIs and how to use them with no code",
"icon": " 🪢",
"url": "https://n8n.io/blog/what-are-apis-how-to-use-them-with-no-code/"
},
{
"label": "5 tasks you can automate with the new Notion API ",
"icon": "⚡️",
"url": "https://n8n.io/blog/5-tasks-you-can-automate-with-notion-api/"
},
{
"label": "Celebrating World Poetry Day with a daily poem in Telegram",
"icon": "📜",
@@ -83,6 +93,11 @@
"icon": "📈",
"url": "https://n8n.io/blog/a-low-code-bitcoin-ticker-built-with-questdb-and-n8n-io/"
},
{
"label": "How Common Knowledge use workflow automation for activism",
"icon": "✨",
"url": "https://n8n.io/blog/automations-for-activists/"
},
{
"label": "Creating scheduled text affirmations with n8n",
"icon": "🤟",
@@ -95,6 +110,12 @@
}
]
},
"alias": [
"API",
"Request",
"URL",
"Build"
],
"subcategories": {
"Core Nodes": [
"Helpers"

Some files were not shown because too many files have changed in this diff Show More