mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
chore: Refactor node parameter assignment logic out of NodeSettings (no-changelog) (#16665)
This commit is contained in:
@@ -8,15 +8,7 @@ import type {
|
||||
NodeParameterValue,
|
||||
INodeCredentialDescription,
|
||||
} from 'n8n-workflow';
|
||||
import {
|
||||
NodeHelpers,
|
||||
NodeConnectionTypes,
|
||||
deepCopy,
|
||||
isINodePropertyCollectionList,
|
||||
isINodePropertiesList,
|
||||
isINodePropertyOptionsList,
|
||||
displayParameter,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeHelpers, NodeConnectionTypes, deepCopy } from 'n8n-workflow';
|
||||
import type {
|
||||
CurlToJSONResponse,
|
||||
INodeUi,
|
||||
@@ -33,8 +25,6 @@ import NodeSettingsTabs from '@/components/NodeSettingsTabs.vue';
|
||||
import NodeWebhooks from '@/components/NodeWebhooks.vue';
|
||||
import NDVSubConnections from '@/components/NDVSubConnections.vue';
|
||||
import get from 'lodash/get';
|
||||
import set from 'lodash/set';
|
||||
import unset from 'lodash/unset';
|
||||
|
||||
import NodeExecuteButton from './NodeExecuteButton.vue';
|
||||
import { isCommunityPackageName } from '@/utils/nodeTypesUtils';
|
||||
@@ -51,9 +41,8 @@ import { useI18n } from '@n8n/i18n';
|
||||
import { useTelemetry } from '@/composables/useTelemetry';
|
||||
import { importCurlEventBus, ndvEventBus } from '@/event-bus';
|
||||
import { ProjectTypes } from '@/types/projects.types';
|
||||
import { updateDynamicConnections } from '@/utils/nodeSettingsUtils';
|
||||
import FreeAiCreditsCallout from '@/components/FreeAiCreditsCallout.vue';
|
||||
import { useCanvasOperations } from '@/composables/useCanvasOperations';
|
||||
import { useNodeSettingsParameters } from '@/composables/useNodeSettingsParameters';
|
||||
import { N8nIconButton } from '@n8n/design-system';
|
||||
|
||||
const props = withDefaults(
|
||||
@@ -104,22 +93,11 @@ const telemetry = useTelemetry();
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
const externalHooks = useExternalHooks();
|
||||
const i18n = useI18n();
|
||||
const canvasOperations = useCanvasOperations();
|
||||
const nodeSettingsParameters = useNodeSettingsParameters();
|
||||
const nodeValues = nodeSettingsParameters.nodeValues;
|
||||
|
||||
const nodeValid = ref(true);
|
||||
const openPanel = ref<'params' | 'settings'>('params');
|
||||
const nodeValues = ref<INodeParameters>({
|
||||
color: '#ff0000',
|
||||
alwaysOutputData: false,
|
||||
executeOnce: false,
|
||||
notesInFlow: false,
|
||||
onError: 'stopWorkflow',
|
||||
retryOnFail: false,
|
||||
maxTries: 3,
|
||||
waitBetweenTries: 1000,
|
||||
notes: '',
|
||||
parameters: {},
|
||||
});
|
||||
|
||||
// Used to prevent nodeValues from being overwritten by defaults on reopening ndv
|
||||
const nodeValuesInitialized = ref(false);
|
||||
@@ -257,126 +235,6 @@ const credentialOwnerName = computed(() => {
|
||||
return credentialsStore.getCredentialOwnerName(credential);
|
||||
});
|
||||
|
||||
const setValue = (name: string, value: NodeParameterValue) => {
|
||||
const nameParts = name.split('.');
|
||||
let lastNamePart: string | undefined = nameParts.pop();
|
||||
|
||||
let isArray = false;
|
||||
if (lastNamePart !== undefined && lastNamePart.includes('[')) {
|
||||
// It includes an index so we have to extract it
|
||||
const lastNameParts = lastNamePart.match(/(.*)\[(\d+)\]$/);
|
||||
if (lastNameParts) {
|
||||
nameParts.push(lastNameParts[1]);
|
||||
lastNamePart = lastNameParts[2];
|
||||
isArray = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the value so that everything updates correctly in the UI
|
||||
if (nameParts.length === 0) {
|
||||
// Data is on top level
|
||||
if (value === null) {
|
||||
// Property should be deleted
|
||||
if (lastNamePart) {
|
||||
const { [lastNamePart]: removedNodeValue, ...remainingNodeValues } = nodeValues.value;
|
||||
nodeValues.value = remainingNodeValues;
|
||||
}
|
||||
} else {
|
||||
// Value should be set
|
||||
nodeValues.value = {
|
||||
...nodeValues.value,
|
||||
[lastNamePart as string]: value,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Data is on lower level
|
||||
if (value === null) {
|
||||
// Property should be deleted
|
||||
let tempValue = get(nodeValues.value, nameParts.join('.')) as
|
||||
| INodeParameters
|
||||
| INodeParameters[];
|
||||
|
||||
if (lastNamePart && !Array.isArray(tempValue)) {
|
||||
const { [lastNamePart]: removedNodeValue, ...remainingNodeValues } = tempValue;
|
||||
tempValue = remainingNodeValues;
|
||||
}
|
||||
|
||||
if (isArray && Array.isArray(tempValue) && tempValue.length === 0) {
|
||||
// If a value from an array got delete and no values are left
|
||||
// delete also the parent
|
||||
lastNamePart = nameParts.pop();
|
||||
tempValue = get(nodeValues.value, nameParts.join('.')) as INodeParameters;
|
||||
if (lastNamePart) {
|
||||
const { [lastNamePart]: removedArrayNodeValue, ...remainingArrayNodeValues } = tempValue;
|
||||
tempValue = remainingArrayNodeValues;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Value should be set
|
||||
if (typeof value === 'object') {
|
||||
set(
|
||||
get(nodeValues.value, nameParts.join('.')) as Record<string, unknown>,
|
||||
lastNamePart as string,
|
||||
deepCopy(value),
|
||||
);
|
||||
} else {
|
||||
set(
|
||||
get(nodeValues.value, nameParts.join('.')) as Record<string, unknown>,
|
||||
lastNamePart as string,
|
||||
value,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nodeValues.value = { ...nodeValues.value };
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes node values that are not valid options for the given parameter.
|
||||
* This can happen when there are multiple node parameters with the same name
|
||||
* but different options and display conditions
|
||||
* @param nodeType The node type description
|
||||
* @param nodeParameterValues Current node parameter values
|
||||
* @param updatedParameter The parameter that was updated. Will be used to determine which parameters to remove based on their display conditions and option values
|
||||
*/
|
||||
const removeMismatchedOptionValues = (
|
||||
nodeType: INodeTypeDescription,
|
||||
nodeParameterValues: INodeParameters | null,
|
||||
updatedParameter: { name: string; value: NodeParameterValue },
|
||||
) => {
|
||||
nodeType.properties.forEach((prop) => {
|
||||
const displayOptions = prop.displayOptions;
|
||||
// Not processing parameters that are not set or don't have options
|
||||
if (!nodeParameterValues?.hasOwnProperty(prop.name) || !displayOptions || !prop.options) {
|
||||
return;
|
||||
}
|
||||
// Only process the parameters that depend on the updated parameter
|
||||
const showCondition = displayOptions.show?.[updatedParameter.name];
|
||||
const hideCondition = displayOptions.hide?.[updatedParameter.name];
|
||||
if (showCondition === undefined && hideCondition === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hasValidOptions = true;
|
||||
|
||||
// Every value should be a possible option
|
||||
if (isINodePropertyCollectionList(prop.options) || isINodePropertiesList(prop.options)) {
|
||||
hasValidOptions = Object.keys(nodeParameterValues).every(
|
||||
(key) => (prop.options ?? []).find((option) => option.name === key) !== undefined,
|
||||
);
|
||||
} else if (isINodePropertyOptionsList(prop.options)) {
|
||||
hasValidOptions = !!prop.options.find(
|
||||
(option) => option.value === nodeParameterValues[prop.name],
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasValidOptions && displayParameter(nodeParameterValues, prop, node.value, nodeType)) {
|
||||
unset(nodeParameterValues as object, prop.name);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const valueChanged = (parameterData: IUpdateInformation) => {
|
||||
let newValue: NodeParameterValue;
|
||||
|
||||
@@ -439,37 +297,16 @@ const valueChanged = (parameterData: IUpdateInformation) => {
|
||||
nodeParameters = deepCopy(nodeParameters);
|
||||
|
||||
if (parameterData.value && typeof parameterData.value === 'object') {
|
||||
for (const parameterName of Object.keys(parameterData.value)) {
|
||||
//@ts-ignore
|
||||
newValue = parameterData.value[parameterName];
|
||||
for (const [parameterName, parameterValue] of Object.entries(parameterData.value)) {
|
||||
newValue = parameterValue;
|
||||
|
||||
// Remove the 'parameters.' from the beginning to just have the
|
||||
// actual parameter name
|
||||
const parameterPath = parameterName.split('.').slice(1).join('.');
|
||||
|
||||
// Check if the path is supposed to change an array and if so get
|
||||
// the needed data like path and index
|
||||
const parameterPathArray = parameterPath.match(/(.*)\[(\d+)\]$/);
|
||||
|
||||
// Apply the new value
|
||||
//@ts-ignore
|
||||
if (parameterData[parameterName] === undefined && parameterPathArray !== null) {
|
||||
// Delete array item
|
||||
const path = parameterPathArray[1];
|
||||
const index = parameterPathArray[2];
|
||||
const data = get(nodeParameters, path);
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
data.splice(parseInt(index, 10), 1);
|
||||
set(nodeParameters as object, path, data);
|
||||
}
|
||||
} else {
|
||||
if (newValue === undefined) {
|
||||
unset(nodeParameters as object, parameterPath);
|
||||
} else {
|
||||
set(nodeParameters as object, parameterPath, newValue);
|
||||
}
|
||||
}
|
||||
const parameterPath = nodeSettingsParameters.updateParameterByPath(
|
||||
parameterName,
|
||||
newValue,
|
||||
nodeParameters,
|
||||
nodeType,
|
||||
_node.typeVersion,
|
||||
);
|
||||
|
||||
void externalHooks.run('nodeSettings.valueChanged', {
|
||||
parameterPath,
|
||||
@@ -492,8 +329,8 @@ const valueChanged = (parameterData: IUpdateInformation) => {
|
||||
);
|
||||
|
||||
for (const key of Object.keys(nodeParameters as object)) {
|
||||
if (nodeParameters && nodeParameters[key] !== null && nodeParameters[key] !== undefined) {
|
||||
setValue(`parameters.${key}`, nodeParameters[key] as string);
|
||||
if (nodeParameters?.[key] !== null && nodeParameters?.[key] !== undefined) {
|
||||
nodeSettingsParameters.setValue(`parameters.${key}`, nodeParameters[key] as string);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -508,127 +345,9 @@ const valueChanged = (parameterData: IUpdateInformation) => {
|
||||
nodeHelpers.updateNodeParameterIssuesByName(_node.name);
|
||||
nodeHelpers.updateNodeCredentialIssuesByName(_node.name);
|
||||
}
|
||||
} else if (parameterData.name.startsWith('parameters.')) {
|
||||
} else if (nodeSettingsParameters.nameIsParameter(parameterData)) {
|
||||
// A node parameter changed
|
||||
|
||||
const nodeType = nodeTypesStore.getNodeType(_node.type, _node.typeVersion);
|
||||
if (!nodeType) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get only the parameters which are different to the defaults
|
||||
let nodeParameters = NodeHelpers.getNodeParameters(
|
||||
nodeType.properties,
|
||||
_node.parameters,
|
||||
false,
|
||||
false,
|
||||
_node,
|
||||
nodeType,
|
||||
);
|
||||
|
||||
const oldNodeParameters = Object.assign({}, nodeParameters);
|
||||
|
||||
// Copy the data because it is the data of vuex so make sure that
|
||||
// we do not edit it directly
|
||||
nodeParameters = deepCopy(nodeParameters);
|
||||
|
||||
// Remove the 'parameters.' from the beginning to just have the
|
||||
// actual parameter name
|
||||
const parameterPath = parameterData.name.split('.').slice(1).join('.');
|
||||
|
||||
// Check if the path is supposed to change an array and if so get
|
||||
// the needed data like path and index
|
||||
const parameterPathArray = parameterPath.match(/(.*)\[(\d+)\]$/);
|
||||
|
||||
// Apply the new value
|
||||
if (parameterData.value === undefined && parameterPathArray !== null) {
|
||||
// Delete array item
|
||||
const path = parameterPathArray[1];
|
||||
const index = parameterPathArray[2];
|
||||
const data = get(nodeParameters, path);
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
data.splice(parseInt(index, 10), 1);
|
||||
set(nodeParameters as object, path, data);
|
||||
}
|
||||
} else {
|
||||
if (newValue === undefined) {
|
||||
unset(nodeParameters as object, parameterPath);
|
||||
} else {
|
||||
set(nodeParameters as object, parameterPath, newValue);
|
||||
}
|
||||
// If value is updated, remove parameter values that have invalid options
|
||||
// so getNodeParameters checks don't fail
|
||||
removeMismatchedOptionValues(nodeType, nodeParameters, {
|
||||
name: parameterPath,
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
// Get the parameters with the now new defaults according to the
|
||||
// from the user actually defined parameters
|
||||
nodeParameters = NodeHelpers.getNodeParameters(
|
||||
nodeType.properties,
|
||||
nodeParameters as INodeParameters,
|
||||
true,
|
||||
false,
|
||||
_node,
|
||||
nodeType,
|
||||
);
|
||||
|
||||
if (isToolNode.value) {
|
||||
const updatedDescription = NodeHelpers.getUpdatedToolDescription(
|
||||
props.nodeType,
|
||||
nodeParameters,
|
||||
node.value?.parameters,
|
||||
);
|
||||
|
||||
if (updatedDescription && nodeParameters) {
|
||||
nodeParameters.toolDescription = updatedDescription;
|
||||
}
|
||||
}
|
||||
|
||||
if (NodeHelpers.isDefaultNodeName(_node.name, nodeType, node.value?.parameters ?? {})) {
|
||||
const newName = NodeHelpers.makeNodeName(nodeParameters ?? {}, nodeType);
|
||||
// Account for unique-ified nodes with `<name><digit>`
|
||||
if (!_node.name.startsWith(newName)) {
|
||||
// We need a timeout here to support events reacting to the valueChange based on node names
|
||||
setTimeout(async () => await canvasOperations.renameNode(_node.name, newName));
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of Object.keys(nodeParameters as object)) {
|
||||
if (nodeParameters && nodeParameters[key] !== null && nodeParameters[key] !== undefined) {
|
||||
setValue(`parameters.${key}`, nodeParameters[key] as string);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the data in vuex
|
||||
const updateInformation: IUpdateInformation = {
|
||||
name: _node.name,
|
||||
value: nodeParameters,
|
||||
};
|
||||
|
||||
const connections = workflowsStore.allConnections;
|
||||
|
||||
const updatedConnections = updateDynamicConnections(_node, connections, parameterData);
|
||||
|
||||
if (updatedConnections) {
|
||||
workflowsStore.setConnections(updatedConnections, true);
|
||||
}
|
||||
|
||||
workflowsStore.setNodeParameters(updateInformation);
|
||||
|
||||
void externalHooks.run('nodeSettings.valueChanged', {
|
||||
parameterPath,
|
||||
newValue,
|
||||
parameters: parameters.value,
|
||||
oldNodeParameters,
|
||||
});
|
||||
|
||||
nodeHelpers.updateNodeParameterIssuesByName(_node.name);
|
||||
nodeHelpers.updateNodeCredentialIssuesByName(_node.name);
|
||||
telemetry.trackNodeParametersValuesChange(nodeType.name, parameterData);
|
||||
nodeSettingsParameters.updateNodeParameter(parameterData, newValue, _node, isToolNode.value);
|
||||
} else {
|
||||
// A property on the node itself changed
|
||||
|
||||
|
||||
Reference in New Issue
Block a user