mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 04:04:06 -08:00
✨ 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:
parent
199377e183
commit
12417ea323
57
packages/nodes-base/credentials/FormIoApi.credentials.ts
Normal file
57
packages/nodes-base/credentials/FormIoApi.credentials.ts
Normal 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: '',
|
||||
},
|
||||
];
|
||||
}
|
205
packages/nodes-base/nodes/FormIo/FormIoTrigger.node.ts
Normal file
205
packages/nodes-base/nodes/FormIo/FormIoTrigger.node.ts
Normal 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),
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
84
packages/nodes-base/nodes/FormIo/GenericFunctions.ts
Normal file
84
packages/nodes-base/nodes/FormIo/GenericFunctions.ts
Normal 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);
|
||||
}
|
||||
}
|
1
packages/nodes-base/nodes/FormIo/formio.svg
Normal file
1
packages/nodes-base/nodes/FormIo/formio.svg
Normal 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 |
|
@ -90,6 +90,7 @@
|
|||
"dist/credentials/FileMaker.credentials.js",
|
||||
"dist/credentials/FlowApi.credentials.js",
|
||||
"dist/credentials/Ftp.credentials.js",
|
||||
"dist/credentials/FormIoApi.credentials.js",
|
||||
"dist/credentials/GetResponseApi.credentials.js",
|
||||
"dist/credentials/GetResponseOAuth2Api.credentials.js",
|
||||
"dist/credentials/GhostAdminApi.credentials.js",
|
||||
|
@ -382,6 +383,7 @@
|
|||
"dist/nodes/Ftp.node.js",
|
||||
"dist/nodes/Freshdesk/Freshdesk.node.js",
|
||||
"dist/nodes/FreshworksCrm/FreshworksCrm.node.js",
|
||||
"dist/nodes/FormIo/FormIoTrigger.node.js",
|
||||
"dist/nodes/Flow/Flow.node.js",
|
||||
"dist/nodes/Flow/FlowTrigger.node.js",
|
||||
"dist/nodes/Function.node.js",
|
||||
|
|
Loading…
Reference in a new issue