Add Form.io Trigger (#2064)

* Initial commit n8n smartsheet and formio nodes
1. created smarsheet node that can add new rew to a particular smarsheet.
2. created formio trigger node which will listen to form.io form submit event and process data.

* Added coloum mapping logic form.io field label to smarsheet column title.
API and smartsheet sheetId configuration added to smartsheet node.

* Added smartsheet api credentials and used it in smartsheet node.

* Added logos to the form.io and smartsheet nodes.
Removed smartsheet and form.io npm dependencies from the nodes.
Added type check for the variables used.
fixed tslint errors in the created nodes.

*  Improvements to #1943

*  Improvements

*  Improvements

*  Some fixes and improvements

Co-authored-by: Parthiban Chandrasekar <pbr.reddys@gmail.com>
Co-authored-by: Jan Oberhauser <jan.oberhauser@gmail.com>
This commit is contained in:
Ricardo Espinoza 2021-08-21 06:53:06 -04:00 committed by GitHub
parent 199377e183
commit 12417ea323
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 349 additions and 0 deletions

View file

@ -0,0 +1,57 @@
import {
ICredentialType,
INodeProperties,
NodePropertyTypes,
} from 'n8n-workflow';
export class FormIoApi implements ICredentialType {
name = 'formIoApi';
displayName = 'Form.io API';
properties: INodeProperties[] = [
{
displayName: 'Environment',
name: 'environment',
type: 'options',
default: 'cloudHosted',
options: [
{
name: 'Cloud-hosted',
value: 'cloudHosted',
},
{
name: 'Self-hosted',
value: 'selfHosted',
},
],
},
{
displayName: 'Self-hosted domain',
name: 'domain',
type: 'string',
default: '',
placeholder: 'https://www.mydomain.com',
displayOptions: {
show: {
environment: [
'selfHosted',
],
},
},
},
{
displayName: 'Email',
name: 'email',
type: 'string' as NodePropertyTypes,
default: '',
},
{
displayName: 'Password',
name: 'password',
type: 'string' as NodePropertyTypes,
typeOptions: {
password: true,
},
default: '',
},
];
}

View file

@ -0,0 +1,205 @@
import {
IHookFunctions,
IWebhookFunctions,
} from 'n8n-core';
import {
ILoadOptionsFunctions,
INodePropertyOptions,
INodeType,
INodeTypeDescription,
IWebhookResponseData,
} from 'n8n-workflow';
import {
formIoApiRequest,
} from './GenericFunctions';
export class FormIoTrigger implements INodeType {
description: INodeTypeDescription = {
displayName: 'Form.io Trigger',
name: 'formIoTrigger',
icon: 'file:formio.svg',
group: ['trigger'],
version: 1,
subtitle: '={{$parameter["event"]}}',
description: 'Handle form.io events via webhooks',
defaults: {
name: 'Form.io Trigger',
color: '#6ad7b9',
},
inputs: [],
outputs: ['main'],
credentials: [
{
name: 'formIoApi',
required: true,
},
],
webhooks: [
{
name: 'default',
httpMethod: 'POST',
responseMode: 'onReceived',
path: 'webhook',
},
],
properties: [
{
displayName: 'Project Name/ID',
name: 'projectId',
type: 'options',
typeOptions: {
loadOptionsMethod: 'getProjects',
},
required: true,
default: '',
description: `Choose from the list or specify an ID. You can also specify the ID using an <a href="https://docs.n8n.io/nodes/expressions.html#expressions" target="_blank" >expression</a>`
},
{
displayName: 'Form Name/ID',
name: 'formId',
type: 'options',
typeOptions: {
loadOptionsDependsOn: [
'projectId',
],
loadOptionsMethod: 'getForms',
},
required: true,
default: '',
description: `Choose from the list or specify an ID. You can also specify the ID using an <a href="https://docs.n8n.io/nodes/expressions.html#expressions" target="_blank" >expression</a>`
},
{
displayName: 'Trigger Events',
name: 'events',
type: 'multiOptions',
options: [
{
name: 'Submission Created',
value: 'create',
},
{
name: 'Submission Updated',
value: 'update',
},
],
required: true,
default: '',
},
{
displayName: 'Simplify Response',
name: 'simple',
type: 'boolean',
default: true,
description: 'Return a simplified version of the response instead of the raw data',
},
],
};
methods = {
loadOptions: {
async getProjects(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const projects = await formIoApiRequest.call(this, 'GET', '/project', {});
const returnData: INodePropertyOptions[] = [];
for (const project of projects) {
returnData.push({
name: project.title,
value: project._id,
});
}
return returnData;
},
async getForms(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
const projectId = this.getCurrentNodeParameter('projectId') as string;
const forms = await formIoApiRequest.call(this, 'GET', `/project/${projectId}/form`, {});
const returnData: INodePropertyOptions[] = [];
for (const form of forms) {
returnData.push({
name: form.title,
value: form._id,
});
}
return returnData;
},
},
};
// @ts-ignore
webhookMethods = {
default: {
async checkExists(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
const webhookUrl = this.getNodeWebhookUrl('default');
const formId = this.getNodeParameter('formId') as string;
const projectId = this.getNodeParameter('projectId') as string;
const method = this.getNodeParameter('events') as string[];
const actions = await formIoApiRequest.call(this, 'GET', `/project/${projectId}/form/${formId}/action`);
for (const action of actions) {
if (action.name === 'webhook') {
if (action.settings.url === webhookUrl &&
// tslint:disable-next-line: no-any
(action.method.length === method.length && action.method.every((value: any) => method.includes(value)))) {
webhookData.webhookId = action._id;
return true;
}
}
}
return false;
},
async create(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
const formId = this.getNodeParameter('formId') as string;
const projectId = this.getNodeParameter('projectId') as string;
const webhookUrl = this.getNodeWebhookUrl('default') as string;
const method = this.getNodeParameter('events') as string[];
const payload = {
data: {
name: `webhook`,
title: `webhook-n8n:${webhookUrl}`,
method,
handler: [
'after',
],
priority: 0,
settings: {
method: 'post',
block: false,
url: webhookUrl,
},
condition: {
field: 'submit',
},
},
};
const webhook = await formIoApiRequest.call(this, 'POST', `/project/${projectId}/form/${formId}/action`, payload);
webhookData.webhookId = webhook._id;
return true;
},
async delete(this: IHookFunctions): Promise<boolean> {
const webhookData = this.getWorkflowStaticData('node');
const formId = this.getNodeParameter('formId') as string;
const projectId = this.getNodeParameter('projectId') as string;
await formIoApiRequest.call(this, 'DELETE', `/project/${projectId}/form/${formId}/action/${webhookData.webhookId}`);
delete webhookData.webhookId;
return true;
},
},
};
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
const req = this.getRequestObject();
const simple = this.getNodeParameter('simple') as boolean;
let response = req.body.request;
if (simple === true) {
response = response.data;
}
return {
workflowData: [
this.helpers.returnJsonArray(response),
],
};
}
}

View file

@ -0,0 +1,84 @@
import {
IExecuteFunctions,
ILoadOptionsFunctions,
} from 'n8n-core';
import {
IDataObject,
IHookFunctions,
IWebhookFunctions,
NodeApiError,
} from 'n8n-workflow';
interface IFormIoCredentials {
environment: 'cloudHosted' | ' selfHosted';
domain?: string;
email: string,
password: string,
}
/**
* Method has the logic to get jwt token from Form.io
* @param this
*/
async function getToken(this: IExecuteFunctions | IWebhookFunctions | IHookFunctions | ILoadOptionsFunctions, credentials: IFormIoCredentials) {
const base = credentials.domain || 'https://formio.form.io';
const options = {
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
body: {
data: {
email: credentials.email,
password: credentials.password,
},
},
uri: `${base}/user/login`,
json: true,
resolveWithFullResponse: true,
};
console.log('options');
console.log(JSON.stringify(options, null, 2));
try {
const responseObject = await this.helpers.request!(options);
return responseObject.headers['x-jwt-token'];
} catch (error) {
throw new Error(`Authentication Failed for Form.io. Please provide valid credentails/ endpoint details`);
}
}
/**
* Method will call register or list webhooks based on the passed method in the parameter
* @param this
* @param method
*/
export async function formIoApiRequest(this: IHookFunctions | ILoadOptionsFunctions | IWebhookFunctions, method: string, endpoint: string, body = {}, qs = {}): Promise<any> { // tslint:disable-line:no-any
const credentials = await this.getCredentials('formIoApi') as unknown as IFormIoCredentials;
const token = await getToken.call(this, credentials);
const base = credentials.domain || 'https://api.form.io';
const options = {
headers: {
'Content-Type': 'application/json',
'x-jwt-token': token,
},
method,
body,
qs,
uri: `${base}${endpoint}`,
json: true,
};
try {
return await this.helpers.request!.call(this, options);
} catch (error) {
throw new NodeApiError(this.getNode(), error);
}
}

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48"><title>favicon-3</title><image width="48" height="48" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAC3FBMVEUAAAAQdbwPdbwPdbwPdbxUsUdUskdUskdUskdVskgQdrwPdbwPdbwPdbwPdbwQdbxUskdUskdUskdUskdVskhVskgUdroQdbwPdbwPdbwPdbwPdbxUskdUskdUskcQdrwPdbwPdbwPdbxUskdUskdUskdUskdUskdWs0kQdrwQdbwPdbwPdbwPdbwQdbxWs0lUskdUskdUskdUskdVskgVeL0RdrwPdbwPdbwPdbwPdbwPdbwQdbxUskdUskdUskdUskdUskdVskkRdrwPdbwPdbwPdbwPdbwPdbwQdbxYskxUskdUskdUskdUskdUskdXs0oSdrwPdbwPdbwPdbwPdbwPdbwQdbwbe75VsklUskdUskdUskdUskdVskgRdrwPdbwPdbwPdbwPdbwQdbxUskdUskdUskdVskhWskkRdrwPdbwPdbwPdbwPdbwQdbxWsklUskdUskdUskdUskdUskcPdbwPdbwPdbwPdbwUd71VskhUskdUskdUskdVskgPdbwPdbwPdbwUeL1VskhUskdUskdUskdUskcPdbwPdbwPdbwQdbxVsklUskdUskdUskdUskdUskdVskgPdbwPdbwPdbwPdbwPdbxUskdUskdUskdUskdUskdUskdUskgTd7wQdbwPdbwPdbwPdbwUeL1VskhUskdUskdVskgPdbwPdbwPdbwPdbwQdbxVs0hUskdUskdUskdUskdUskcQdbwPdbwPdbwPdbwRdrxUskhUskdUskdUskdVskgWeb4QdbwPdbwPdbwPdbwPdbxVskhUskdUskdUskdVskgRdrwPdbwPdbwPdbxUskdUskdUskdUskdWskkPdbwPdbwPdbwPdbxUskdUskdUskdUskcSdr0PdbwPdbwPdbwQdbxUskdUskcQdbwPdbwPdbwPdbwPdbwXeL5UskdUskdUskdVskkQdbwPdbwRdrxUskdUskcPdbxUskf///9ORprqAAAA8XRSTlMAGmZpGwE4d0kKCErK/KkQOt3znicBATin8Oou/n8ZE3Lisz7l/b9HBQZBuvalIgFMz++NIgEflO/+wVAMHXjk2WEMEHDV/eF9GwE5pvW2RAQFQbX6+LA1AQlg1uuKIR2M7M9cDiKI01wKA2DS6YQhBUCv+rAwL+S2QwIPbdipCE/jTgIOifvRFBq+kx4CQ7v4+X0CIefOaxUBL5Lo/M0LBDq9uDcDDGLeGhPY5oEXAjT3sj8HKY7JYgsii8xnEAM8t/ekOA1dy+wcEW9hAhOdwUIEMpnz22PbbxgHVsPZH17xFoLe3GUBoMpRBgYdBCEWUIfmiQAAAAFiS0dE86yxvu4AAAAHdElNRQflBgsXNAUANeC9AAAB60lEQVRIx2NgGAWjgFaAkYmZBcxgZWPn4CSonIubh/cjHz+IKSD4SUhYRBSvcjFxCcmPHz9KSYM47J8+fZIRlJXDo15eQfEjCCiBbVBWAer4pKqmroFDuaaWNi9IuY6uHpivb2CoCtJiZGyCzV2mZuYWIOWWVtY2MDFbO3uQDhkHRycM9c4urm4g9e4enkiiXt4+viAtvn7+ASjKA4OCQ0DKQ8PCI1ANioyKlgFpiYmNQxKNT0gEuyYpOQXTralpMWB3pWdkQkWysnPArsnNy8caGAWFRcVgd5WUQgTKysGuqaiswhXc1TW1YHfV1YO5DWDnNzY1446gltY2kIb2DjCvswscW1LdPTiU9/b1TwCpnzhpMkRgSsJUkA63adNnYFE+c9bsOSDlc+c5zoeJLVi4COws3sVLlqKrX7Z8xSdIfK9EFl7FtBrsrjVr16EoX7+haCNIefGmzVvQTNq6Dewu3u07diIEd+2GpI09e/dhunX/gYNgdx06fAQaXkePHQe75kTsSeyhcep0OcRdZ86CuOfOg8Nm7oWLl3AF99nLV66CdFwDp+/r4LC/cfMWAx5w+85dYJ64dx/EfjD306eH50sZCIBHj588ffYc7KQX9i9fvSaknoHhzdu376Cefv9BlLD6UTAK6AUAHXsSvNupchsAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDYtMTFUMjM6NTI6MDQrMDA6MDBy76P8AAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTA2LTExVDIzOjUyOjA0KzAwOjAwA7IbQAAAAABJRU5ErkJggg=="/></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -90,6 +90,7 @@
"dist/credentials/FileMaker.credentials.js", "dist/credentials/FileMaker.credentials.js",
"dist/credentials/FlowApi.credentials.js", "dist/credentials/FlowApi.credentials.js",
"dist/credentials/Ftp.credentials.js", "dist/credentials/Ftp.credentials.js",
"dist/credentials/FormIoApi.credentials.js",
"dist/credentials/GetResponseApi.credentials.js", "dist/credentials/GetResponseApi.credentials.js",
"dist/credentials/GetResponseOAuth2Api.credentials.js", "dist/credentials/GetResponseOAuth2Api.credentials.js",
"dist/credentials/GhostAdminApi.credentials.js", "dist/credentials/GhostAdminApi.credentials.js",
@ -382,6 +383,7 @@
"dist/nodes/Ftp.node.js", "dist/nodes/Ftp.node.js",
"dist/nodes/Freshdesk/Freshdesk.node.js", "dist/nodes/Freshdesk/Freshdesk.node.js",
"dist/nodes/FreshworksCrm/FreshworksCrm.node.js", "dist/nodes/FreshworksCrm/FreshworksCrm.node.js",
"dist/nodes/FormIo/FormIoTrigger.node.js",
"dist/nodes/Flow/Flow.node.js", "dist/nodes/Flow/Flow.node.js",
"dist/nodes/Flow/FlowTrigger.node.js", "dist/nodes/Flow/FlowTrigger.node.js",
"dist/nodes/Function.node.js", "dist/nodes/Function.node.js",