mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(n8n Form Page Node): New node (#10390)
Co-authored-by: Giulio Andreini <g.andreini@gmail.com> Co-authored-by: Shireen Missi <94372015+ShireenMissi@users.noreply.github.com>
This commit is contained in:
@@ -38,6 +38,7 @@ export const FUNCTION_NODE_TYPE = 'n8n-nodes-base.function';
|
||||
export const FUNCTION_ITEM_NODE_TYPE = 'n8n-nodes-base.functionItem';
|
||||
export const MERGE_NODE_TYPE = 'n8n-nodes-base.merge';
|
||||
export const AI_TRANSFORM_NODE_TYPE = 'n8n-nodes-base.aiTransform';
|
||||
export const FORM_NODE_TYPE = 'n8n-nodes-base.form';
|
||||
export const FORM_TRIGGER_NODE_TYPE = 'n8n-nodes-base.formTrigger';
|
||||
export const CHAT_TRIGGER_NODE_TYPE = '@n8n/n8n-nodes-langchain.chatTrigger';
|
||||
export const WAIT_NODE_TYPE = 'n8n-nodes-base.wait';
|
||||
@@ -56,6 +57,8 @@ export const SCRIPTING_NODE_TYPES = [
|
||||
AI_TRANSFORM_NODE_TYPE,
|
||||
];
|
||||
|
||||
export const ADD_FORM_NOTICE = 'addFormPage';
|
||||
|
||||
/**
|
||||
* Nodes whose parameter values may refer to other nodes without expressions.
|
||||
* Their content may need to be updated when the referenced node is renamed.
|
||||
|
||||
@@ -1111,6 +1111,7 @@ export interface IWebhookFunctions extends FunctionsBaseWithRequiredKeys<'getMod
|
||||
options?: IGetNodeParameterOptions,
|
||||
): NodeParameterValueType | object;
|
||||
getNodeWebhookUrl: (name: string) => string | undefined;
|
||||
evaluateExpression(expression: string, itemIndex?: number): NodeParameterValueType;
|
||||
getParamsData(): object;
|
||||
getQueryData(): object;
|
||||
getRequestObject(): express.Request;
|
||||
@@ -2026,7 +2027,7 @@ export interface IWebhookResponseData {
|
||||
}
|
||||
|
||||
export type WebhookResponseData = 'allEntries' | 'firstEntryJson' | 'firstEntryBinary' | 'noData';
|
||||
export type WebhookResponseMode = 'onReceived' | 'lastNode' | 'responseNode';
|
||||
export type WebhookResponseMode = 'onReceived' | 'lastNode' | 'responseNode' | 'formPage';
|
||||
|
||||
export interface INodeTypes {
|
||||
getByName(nodeType: string): INodeType | IVersionedNodeType;
|
||||
@@ -2584,6 +2585,18 @@ export interface ResourceMapperField {
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export type FormFieldsParameter = Array<{
|
||||
fieldLabel: string;
|
||||
fieldType?: string;
|
||||
requiredField?: boolean;
|
||||
fieldOptions?: { values: Array<{ option: string }> };
|
||||
multiselect?: boolean;
|
||||
multipleFiles?: boolean;
|
||||
acceptFileTypes?: string;
|
||||
formatDate?: string;
|
||||
placeholder?: string;
|
||||
}>;
|
||||
|
||||
export type FieldTypeMap = {
|
||||
// eslint-disable-next-line id-denylist
|
||||
boolean: boolean;
|
||||
@@ -2599,6 +2612,7 @@ export type FieldTypeMap = {
|
||||
options: any;
|
||||
url: string;
|
||||
jwt: string;
|
||||
'form-fields': FormFieldsParameter;
|
||||
};
|
||||
|
||||
export type FieldType = keyof FieldTypeMap;
|
||||
|
||||
@@ -2,7 +2,12 @@ import isObject from 'lodash/isObject';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { ApplicationError } from './errors';
|
||||
import type { FieldType, INodePropertyOptions, ValidationResult } from './Interfaces';
|
||||
import type {
|
||||
FieldType,
|
||||
FormFieldsParameter,
|
||||
INodePropertyOptions,
|
||||
ValidationResult,
|
||||
} from './Interfaces';
|
||||
import { jsonParse } from './utils';
|
||||
|
||||
export const tryToParseNumber = (value: unknown): number => {
|
||||
@@ -148,6 +153,96 @@ export const tryToParseObject = (value: unknown): object => {
|
||||
}
|
||||
};
|
||||
|
||||
const ALLOWED_FORM_FIELDS_KEYS = [
|
||||
'fieldLabel',
|
||||
'fieldType',
|
||||
'placeholder',
|
||||
'fieldOptions',
|
||||
'multiselect',
|
||||
'multipleFiles',
|
||||
'acceptFileTypes',
|
||||
'formatDate',
|
||||
'requiredField',
|
||||
];
|
||||
|
||||
const ALLOWED_FIELD_TYPES = [
|
||||
'date',
|
||||
'dropdown',
|
||||
'email',
|
||||
'file',
|
||||
'number',
|
||||
'password',
|
||||
'text',
|
||||
'textarea',
|
||||
];
|
||||
|
||||
export const tryToParseJsonToFormFields = (value: unknown): FormFieldsParameter => {
|
||||
const fields: FormFieldsParameter = [];
|
||||
|
||||
try {
|
||||
const rawFields = jsonParse<Array<{ [key: string]: unknown }>>(value as string, {
|
||||
acceptJSObject: true,
|
||||
});
|
||||
|
||||
for (const [index, field] of rawFields.entries()) {
|
||||
for (const key of Object.keys(field)) {
|
||||
if (!ALLOWED_FORM_FIELDS_KEYS.includes(key)) {
|
||||
throw new ApplicationError(`Key '${key}' in field ${index} is not valid for form fields`);
|
||||
}
|
||||
if (
|
||||
key !== 'fieldOptions' &&
|
||||
!['string', 'number', 'boolean'].includes(typeof field[key])
|
||||
) {
|
||||
field[key] = String(field[key]);
|
||||
} else if (typeof field[key] === 'string') {
|
||||
field[key] = field[key].replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
if (key === 'fieldType' && !ALLOWED_FIELD_TYPES.includes(field[key] as string)) {
|
||||
throw new ApplicationError(
|
||||
`Field type '${field[key] as string}' in field ${index} is not valid for form fields`,
|
||||
);
|
||||
}
|
||||
|
||||
if (key === 'fieldOptions') {
|
||||
if (Array.isArray(field[key])) {
|
||||
field[key] = { values: field[key] };
|
||||
}
|
||||
|
||||
if (
|
||||
typeof field[key] !== 'object' ||
|
||||
!(field[key] as { [key: string]: unknown }).values
|
||||
) {
|
||||
throw new ApplicationError(
|
||||
`Field dropdown in field ${index} does has no 'values' property that contain an array of options`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const [optionIndex, option] of (
|
||||
(field[key] as { [key: string]: unknown }).values as Array<{
|
||||
[key: string]: { option: string };
|
||||
}>
|
||||
).entries()) {
|
||||
if (Object.keys(option).length !== 1 || typeof option.option !== 'string') {
|
||||
throw new ApplicationError(
|
||||
`Field dropdown in field ${index} has an invalid option ${optionIndex}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fields.push(field as FormFieldsParameter[number]);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ApplicationError) throw error;
|
||||
|
||||
throw new ApplicationError('Value is not valid JSON');
|
||||
}
|
||||
|
||||
return fields;
|
||||
};
|
||||
|
||||
export const getValueDescription = <T>(value: T): string => {
|
||||
if (typeof value === 'object') {
|
||||
if (value === null) return "'null'";
|
||||
@@ -325,6 +420,16 @@ export function validateFieldType(
|
||||
};
|
||||
}
|
||||
}
|
||||
case 'form-fields': {
|
||||
try {
|
||||
return { valid: true, newValue: tryToParseJsonToFormFields(value) };
|
||||
} catch (e) {
|
||||
return {
|
||||
valid: false,
|
||||
errorMessage: (e as Error).message,
|
||||
};
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return { valid: true, newValue: value };
|
||||
}
|
||||
|
||||
@@ -1365,6 +1365,7 @@ export class WorkflowDataProxy {
|
||||
$thisRunIndex: this.runIndex,
|
||||
$nodeVersion: that.workflow.getNode(that.activeNodeName)?.typeVersion,
|
||||
$nodeId: that.workflow.getNode(that.activeNodeName)?.id,
|
||||
$webhookId: that.workflow.getNode(that.activeNodeName)?.webhookId,
|
||||
};
|
||||
|
||||
return new Proxy(base, {
|
||||
|
||||
@@ -121,7 +121,7 @@ type JSONParseOptions<T> = { acceptJSObject?: boolean } & MutuallyExclusive<
|
||||
*
|
||||
* @param {string} jsonString - The JSON string to parse.
|
||||
* @param {Object} [options] - Optional settings for parsing the JSON string. Either `fallbackValue` or `errorMessage` can be set, but not both.
|
||||
* @param {boolean} [options.parseJSObject=false] - If true, attempts to recover from common JSON format errors by parsing the JSON string as a JavaScript Object.
|
||||
* @param {boolean} [options.acceptJSObject=false] - If true, attempts to recover from common JSON format errors by parsing the JSON string as a JavaScript Object.
|
||||
* @param {string} [options.errorMessage] - A custom error message to throw if the JSON string cannot be parsed.
|
||||
* @param {*} [options.fallbackValue] - A fallback value to return if the JSON string cannot be parsed.
|
||||
* @returns {Object} - The parsed object, or the fallback value if parsing fails and `fallbackValue` is set.
|
||||
|
||||
Reference in New Issue
Block a user