mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-20 19:32:15 +00:00
feat(editor): Show the right editor in focus panel (#17062)
Co-authored-by: Charlie Kolb <charlie@n8n.io>
This commit is contained in:
@@ -3,10 +3,9 @@ import type {
|
||||
CalloutActionType,
|
||||
INodeParameters,
|
||||
INodeProperties,
|
||||
NodeParameterValue,
|
||||
NodeParameterValueType,
|
||||
} from 'n8n-workflow';
|
||||
import { ADD_FORM_NOTICE, deepCopy, getParameterValueByPath, NodeHelpers } from 'n8n-workflow';
|
||||
import { ADD_FORM_NOTICE, getParameterValueByPath, NodeHelpers } from 'n8n-workflow';
|
||||
import { computed, defineAsyncComponent, onErrorCaptured, ref, watch, type WatchSource } from 'vue';
|
||||
|
||||
import type { INodeUi, IUpdateInformation } from '@/Interface';
|
||||
@@ -19,7 +18,7 @@ import MultipleParameter from '@/components/MultipleParameter.vue';
|
||||
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||
import ResourceMapper from '@/components/ResourceMapper/ResourceMapper.vue';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||
import { useNodeSettingsParameters } from '@/composables/useNodeSettingsParameters';
|
||||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||
import { useMessage } from '@/composables/useMessage';
|
||||
import {
|
||||
@@ -32,15 +31,9 @@ import {
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
|
||||
import {
|
||||
getMainAuthField,
|
||||
getNodeAuthFields,
|
||||
isAuthRelatedParameter,
|
||||
} from '@/utils/nodeTypesUtils';
|
||||
import { captureException } from '@sentry/vue';
|
||||
import { computedWithControl } from '@vueuse/core';
|
||||
import get from 'lodash/get';
|
||||
import set from 'lodash/set';
|
||||
import {
|
||||
N8nCallout,
|
||||
N8nIcon,
|
||||
@@ -52,6 +45,7 @@ import {
|
||||
} from '@n8n/design-system';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useCalloutHelpers } from '@/composables/useCalloutHelpers';
|
||||
import { getParameterTypeOption } from '@/utils/nodeSettingsUtils';
|
||||
|
||||
const LazyFixedCollectionParameter = defineAsyncComponent(
|
||||
async () => await import('./FixedCollectionParameter.vue'),
|
||||
@@ -86,7 +80,7 @@ const nodeTypesStore = useNodeTypesStore();
|
||||
const ndvStore = useNDVStore();
|
||||
|
||||
const message = useMessage();
|
||||
const nodeHelpers = useNodeHelpers();
|
||||
const nodeSettingsParameters = useNodeSettingsParameters();
|
||||
const asyncLoadingError = ref(false);
|
||||
const workflowHelpers = useWorkflowHelpers();
|
||||
const i18n = useI18n();
|
||||
@@ -114,6 +108,8 @@ onErrorCaptured((e, component) => {
|
||||
return false;
|
||||
});
|
||||
|
||||
const node = computed(() => props.node ?? ndvStore.activeNode);
|
||||
|
||||
const nodeType = computed(() => {
|
||||
if (node.value) {
|
||||
return nodeTypesStore.getNodeType(node.value.type, node.value.typeVersion);
|
||||
@@ -121,13 +117,11 @@ const nodeType = computed(() => {
|
||||
return null;
|
||||
});
|
||||
|
||||
const node = computed(() => props.node ?? ndvStore.activeNode);
|
||||
|
||||
const filteredParameters = computedWithControl(
|
||||
[() => props.parameters, () => props.nodeValues] as WatchSource[],
|
||||
() => {
|
||||
const parameters = props.parameters.filter((parameter: INodeProperties) =>
|
||||
displayNodeParameter(parameter),
|
||||
shouldDisplayNodeParameter(parameter),
|
||||
);
|
||||
|
||||
if (node.value && node.value.type === FORM_TRIGGER_NODE_TYPE) {
|
||||
@@ -154,10 +148,6 @@ const filteredParameterNames = computed(() => {
|
||||
return filteredParameters.value.map((parameter) => parameter.name);
|
||||
});
|
||||
|
||||
const nodeAuthFields = computed(() => {
|
||||
return getNodeAuthFields(nodeType.value);
|
||||
});
|
||||
|
||||
const credentialsParameterIndex = computed(() => {
|
||||
return filteredParameters.value.findIndex((parameter) => parameter.type === 'credentials');
|
||||
});
|
||||
@@ -182,10 +172,6 @@ const indexToShowSlotAt = computed(() => {
|
||||
return Math.min(index, filteredParameters.value.length - 1);
|
||||
});
|
||||
|
||||
const mainNodeAuthField = computed(() => {
|
||||
return getMainAuthField(nodeType.value || null);
|
||||
});
|
||||
|
||||
watch(filteredParameterNames, (newValue, oldValue) => {
|
||||
if (newValue === undefined) {
|
||||
return;
|
||||
@@ -210,8 +196,8 @@ function updateFormTriggerParameters(parameters: INodeProperties[], triggerName:
|
||||
const connectedNodes = workflow.getChildNodes(triggerName);
|
||||
|
||||
const hasFormPage = connectedNodes.some((nodeName) => {
|
||||
const node = workflow.getNode(nodeName);
|
||||
return node && node.type === FORM_NODE_TYPE;
|
||||
const _node = workflow.getNode(nodeName);
|
||||
return _node && _node.type === FORM_NODE_TYPE;
|
||||
});
|
||||
|
||||
if (hasFormPage) {
|
||||
@@ -255,15 +241,15 @@ function updateWaitParameters(parameters: INodeProperties[], nodeName: string) {
|
||||
const parentNodes = workflow.getParentNodes(nodeName);
|
||||
|
||||
const formTriggerName = parentNodes.find(
|
||||
(node) => workflow.nodes[node].type === FORM_TRIGGER_NODE_TYPE,
|
||||
(_node) => workflow.nodes[_node].type === FORM_TRIGGER_NODE_TYPE,
|
||||
);
|
||||
if (!formTriggerName) return parameters;
|
||||
|
||||
const connectedNodes = workflow.getChildNodes(formTriggerName);
|
||||
|
||||
const hasFormPage = connectedNodes.some((nodeName) => {
|
||||
const node = workflow.getNode(nodeName);
|
||||
return node && node.type === FORM_NODE_TYPE;
|
||||
const hasFormPage = connectedNodes.some((_nodeName) => {
|
||||
const _node = workflow.getNode(_nodeName);
|
||||
return _node && _node.type === FORM_NODE_TYPE;
|
||||
});
|
||||
|
||||
if (hasFormPage) {
|
||||
@@ -294,7 +280,7 @@ function updateFormParameters(parameters: INodeProperties[], nodeName: string) {
|
||||
const parentNodes = workflow.getParentNodes(nodeName);
|
||||
|
||||
const formTriggerName = parentNodes.find(
|
||||
(node) => workflow.nodes[node].type === FORM_TRIGGER_NODE_TYPE,
|
||||
(_node) => workflow.nodes[_node].type === FORM_TRIGGER_NODE_TYPE,
|
||||
);
|
||||
|
||||
if (formTriggerName) return parameters.filter((parameter) => parameter.name !== 'triggerNotice');
|
||||
@@ -321,22 +307,7 @@ function getCredentialsDependencies() {
|
||||
}
|
||||
|
||||
function multipleValues(parameter: INodeProperties): boolean {
|
||||
return getArgument('multipleValues', parameter) === true;
|
||||
}
|
||||
|
||||
function getArgument(
|
||||
argumentName: string,
|
||||
parameter: INodeProperties,
|
||||
): string | string[] | number | boolean | undefined {
|
||||
if (parameter.typeOptions === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (parameter.typeOptions[argumentName] === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return parameter.typeOptions[argumentName];
|
||||
return getParameterTypeOption(parameter, 'multipleValues') === true;
|
||||
}
|
||||
|
||||
function getPath(parameterName: string): string {
|
||||
@@ -354,116 +325,15 @@ function deleteOption(optionName: string): void {
|
||||
emit('valueChanged', parameterData);
|
||||
}
|
||||
|
||||
function mustHideDuringCustomApiCall(
|
||||
parameter: INodeProperties,
|
||||
nodeValues: INodeParameters,
|
||||
): boolean {
|
||||
if (parameter?.displayOptions?.hide) return true;
|
||||
|
||||
const MUST_REMAIN_VISIBLE = [
|
||||
'authentication',
|
||||
'resource',
|
||||
'operation',
|
||||
...Object.keys(nodeValues),
|
||||
];
|
||||
|
||||
return !MUST_REMAIN_VISIBLE.includes(parameter.name);
|
||||
}
|
||||
|
||||
function displayNodeParameter(
|
||||
function shouldDisplayNodeParameter(
|
||||
parameter: INodeProperties,
|
||||
displayKey: 'displayOptions' | 'disabledOptions' = 'displayOptions',
|
||||
): boolean {
|
||||
if (parameter.type === 'hidden') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
nodeHelpers.isCustomApiCallSelected(props.nodeValues) &&
|
||||
mustHideDuringCustomApiCall(parameter, props.nodeValues)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Hide authentication related fields since it will now be part of credentials modal
|
||||
if (
|
||||
!KEEP_AUTH_IN_NDV_FOR_NODES.includes(node.value?.type || '') &&
|
||||
mainNodeAuthField.value &&
|
||||
(parameter.name === mainNodeAuthField.value?.name || shouldHideAuthRelatedParameter(parameter))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parameter[displayKey] === undefined) {
|
||||
// If it is not defined no need to do a proper check
|
||||
return true;
|
||||
}
|
||||
|
||||
const nodeValues: INodeParameters = {};
|
||||
let rawValues = props.nodeValues;
|
||||
if (props.path) {
|
||||
rawValues = get(props.nodeValues, props.path) as INodeParameters;
|
||||
}
|
||||
|
||||
if (!rawValues) {
|
||||
return false;
|
||||
}
|
||||
// Resolve expressions
|
||||
const resolveKeys = Object.keys(rawValues);
|
||||
let key: string;
|
||||
let i = 0;
|
||||
let parameterGotResolved = false;
|
||||
do {
|
||||
key = resolveKeys.shift() as string;
|
||||
const value = rawValues[key];
|
||||
if (typeof value === 'string' && value?.charAt(0) === '=') {
|
||||
// Contains an expression that
|
||||
if (
|
||||
value.includes('$parameter') &&
|
||||
resolveKeys.some((parameterName) => value.includes(parameterName))
|
||||
) {
|
||||
// Contains probably an expression of a missing parameter so skip
|
||||
resolveKeys.push(key);
|
||||
continue;
|
||||
} else {
|
||||
// Contains probably no expression with a missing parameter so resolve
|
||||
try {
|
||||
nodeValues[key] = workflowHelpers.resolveExpression(
|
||||
value,
|
||||
nodeValues,
|
||||
) as NodeParameterValue;
|
||||
} catch (e) {
|
||||
// If expression is invalid ignore
|
||||
nodeValues[key] = '';
|
||||
}
|
||||
parameterGotResolved = true;
|
||||
}
|
||||
} else {
|
||||
// Does not contain an expression, add directly
|
||||
nodeValues[key] = rawValues[key];
|
||||
}
|
||||
// TODO: Think about how to calculate this best
|
||||
if (i++ > 50) {
|
||||
// Make sure we do not get caught
|
||||
break;
|
||||
}
|
||||
} while (resolveKeys.length !== 0);
|
||||
|
||||
if (parameterGotResolved) {
|
||||
if (props.path) {
|
||||
rawValues = deepCopy(props.nodeValues);
|
||||
set(rawValues, props.path, nodeValues);
|
||||
return nodeHelpers.displayParameter(rawValues, parameter, props.path, node.value, displayKey);
|
||||
} else {
|
||||
return nodeHelpers.displayParameter(nodeValues, parameter, '', node.value, displayKey);
|
||||
}
|
||||
}
|
||||
|
||||
return nodeHelpers.displayParameter(
|
||||
return nodeSettingsParameters.shouldDisplayNodeParameter(
|
||||
props.nodeValues,
|
||||
node.value,
|
||||
parameter,
|
||||
props.path,
|
||||
node.value,
|
||||
displayKey,
|
||||
);
|
||||
}
|
||||
@@ -493,26 +363,15 @@ function getParameterIssues(parameter: INodeProperties): string[] {
|
||||
return issues.parameters?.[parameter.name] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles default node button parameter type actions
|
||||
* @param parameter
|
||||
*/
|
||||
|
||||
function shouldHideAuthRelatedParameter(parameter: INodeProperties): boolean {
|
||||
// TODO: For now, hide all fields that are used in authentication fields displayOptions
|
||||
// Ideally, we should check if any non-auth field depends on it before hiding it but
|
||||
// since there is no such case, omitting it to avoid additional computation
|
||||
return isAuthRelatedParameter(nodeAuthFields.value, parameter);
|
||||
}
|
||||
|
||||
function shouldShowOptions(parameter: INodeProperties): boolean {
|
||||
return parameter.type !== 'resourceMapper';
|
||||
}
|
||||
|
||||
function getDependentParametersValues(parameter: INodeProperties): string | null {
|
||||
const loadOptionsDependsOn = getArgument('loadOptionsDependsOn', parameter) as
|
||||
| string[]
|
||||
| undefined;
|
||||
const loadOptionsDependsOn = getParameterTypeOption<string[] | undefined>(
|
||||
parameter,
|
||||
'loadOptionsDependsOn',
|
||||
);
|
||||
|
||||
if (loadOptionsDependsOn === undefined) {
|
||||
return null;
|
||||
@@ -529,7 +388,7 @@ function getDependentParametersValues(parameter: INodeProperties): string | null
|
||||
}
|
||||
|
||||
return returnValues.join('|');
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -792,7 +651,7 @@ const onCalloutDismiss = async (parameter: INodeProperties) => {
|
||||
:path="getPath(parameter.name)"
|
||||
:is-read-only="
|
||||
isReadOnly ||
|
||||
(parameter.disabledOptions && displayNodeParameter(parameter, 'disabledOptions'))
|
||||
(parameter.disabledOptions && shouldDisplayNodeParameter(parameter, 'disabledOptions'))
|
||||
"
|
||||
:hide-label="false"
|
||||
:node-values="nodeValues"
|
||||
|
||||
Reference in New Issue
Block a user