mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 09:36:44 +00:00
fix(editor): Make expression resolution work in embedded ndv (no-changelog) (#17221)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import ExperimentalCanvasNodeSettings from './ExperimentalCanvasNodeSettings.vue';
|
||||
import { onBeforeUnmount, ref, computed } from 'vue';
|
||||
import { onBeforeUnmount, ref, computed, provide } from 'vue';
|
||||
import { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useExperimentalNdvStore } from '../experimentalNdv.store';
|
||||
@@ -8,6 +8,9 @@ import NodeTitle from '@/components/NodeTitle.vue';
|
||||
import { N8nIcon, N8nIconButton } from '@n8n/design-system';
|
||||
import { useVueFlow } from '@vue-flow/core';
|
||||
import { watchOnce } from '@vueuse/core';
|
||||
import { ExpressionLocalResolveContextSymbol } from '@/constants';
|
||||
import { useEnvironmentsStore } from '@/stores/environments.ee.store';
|
||||
import type { ExpressionLocalResolveContext } from '@/types/expressions';
|
||||
|
||||
const { nodeId, isReadOnly, isConfigurable } = defineProps<{
|
||||
nodeId: string;
|
||||
@@ -56,6 +59,55 @@ const isVisible = computed(() =>
|
||||
);
|
||||
const isOnceVisible = ref(isVisible.value);
|
||||
|
||||
provide(
|
||||
ExpressionLocalResolveContextSymbol,
|
||||
computed<ExpressionLocalResolveContext | undefined>(() => {
|
||||
if (!node.value) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const workflow = workflowsStore.getCurrentWorkflow();
|
||||
const runIndex = 0; // not changeable for now
|
||||
const execution = workflowsStore.workflowExecutionData;
|
||||
const nodeName = node.value.name;
|
||||
|
||||
function findInputNode(): ExpressionLocalResolveContext['inputNode'] {
|
||||
const taskData = (execution?.data?.resultData.runData[nodeName] ?? [])[runIndex];
|
||||
const source = taskData?.source[0];
|
||||
|
||||
if (source) {
|
||||
return {
|
||||
name: source.previousNode,
|
||||
branchIndex: source.previousNodeOutput ?? 0,
|
||||
runIndex: source.previousNodeRun ?? 0,
|
||||
};
|
||||
}
|
||||
|
||||
const inputs = workflow.getParentNodesByDepth(nodeName, 1);
|
||||
|
||||
if (inputs.length > 0) {
|
||||
return {
|
||||
name: inputs[0].name,
|
||||
branchIndex: inputs[0].indicies[0] ?? 0,
|
||||
runIndex: 0,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
localResolve: true,
|
||||
envVars: useEnvironmentsStore().variablesAsObject,
|
||||
workflow,
|
||||
execution,
|
||||
nodeName,
|
||||
additionalKeys: {},
|
||||
inputNode: findInputNode(),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
watchOnce(isVisible, (visible) => {
|
||||
isOnceVisible.value = isOnceVisible.value || visible;
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
computed,
|
||||
inject,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
@@ -15,7 +16,7 @@ import { ensureSyntaxTree } from '@codemirror/language';
|
||||
import type { IDataObject } from 'n8n-workflow';
|
||||
import { Expression, ExpressionExtensions } from 'n8n-workflow';
|
||||
|
||||
import { EXPRESSION_EDITOR_PARSER_TIMEOUT } from '@/constants';
|
||||
import { EXPRESSION_EDITOR_PARSER_TIMEOUT, ExpressionLocalResolveContextSymbol } from '@/constants';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
|
||||
import type { TargetItem, TargetNodeParameterContext } from '@/Interface';
|
||||
@@ -75,6 +76,10 @@ export const useExpressionEditor = ({
|
||||
const autocompleteStatus = ref<'pending' | 'active' | null>(null);
|
||||
const dragging = ref(false);
|
||||
const hasChanges = ref(false);
|
||||
const expressionLocalResolveContext = inject(
|
||||
ExpressionLocalResolveContextSymbol,
|
||||
computed(() => undefined),
|
||||
);
|
||||
|
||||
const emitChanges = debounce(onChange, 300);
|
||||
|
||||
@@ -307,7 +312,12 @@ export const useExpressionEditor = ({
|
||||
};
|
||||
|
||||
try {
|
||||
if (!ndvStore.activeNode && toValue(targetNodeParameterContext) === undefined) {
|
||||
if (expressionLocalResolveContext.value) {
|
||||
result.resolved = workflowHelpers.resolveExpression('=' + resolvable, undefined, {
|
||||
...expressionLocalResolveContext.value,
|
||||
additionalKeys: toValue(additionalData),
|
||||
});
|
||||
} else if (!ndvStore.activeNode && toValue(targetNodeParameterContext) === undefined) {
|
||||
// e.g. credential modal
|
||||
result.resolved = Expression.resolveWithoutWorkflow(resolvable, toValue(additionalData));
|
||||
} else {
|
||||
|
||||
@@ -4,8 +4,19 @@ import { isExpression as isExpressionUtil, stringifyExpressionResult } from '@/u
|
||||
|
||||
import debounce from 'lodash/debounce';
|
||||
import { createResultError, createResultOk, type IDataObject, type Result } from 'n8n-workflow';
|
||||
import { computed, onMounted, ref, toRef, toValue, watch, type MaybeRefOrGetter } from 'vue';
|
||||
import {
|
||||
computed,
|
||||
onMounted,
|
||||
ref,
|
||||
toRef,
|
||||
toValue,
|
||||
inject,
|
||||
type MaybeRefOrGetter,
|
||||
watch,
|
||||
} from 'vue';
|
||||
import { useWorkflowHelpers, type ResolveParameterOptions } from './useWorkflowHelpers';
|
||||
import { ExpressionLocalResolveContextSymbol } from '@/constants';
|
||||
import type { ExpressionLocalResolveContext } from '@/types/expressions';
|
||||
|
||||
export function useResolvedExpression({
|
||||
expression,
|
||||
@@ -25,6 +36,11 @@ export function useResolvedExpression({
|
||||
|
||||
const { resolveExpression } = useWorkflowHelpers();
|
||||
|
||||
const expressionLocalResolveCtx = inject(
|
||||
ExpressionLocalResolveContextSymbol,
|
||||
computed(() => undefined),
|
||||
);
|
||||
|
||||
const resolvedExpression = ref<unknown>(null);
|
||||
const resolvedExpressionString = ref('');
|
||||
|
||||
@@ -37,29 +53,27 @@ export function useResolvedExpression({
|
||||
);
|
||||
const isExpression = computed(() => isExpressionUtil(toValue(expression)));
|
||||
|
||||
function resolve(): Result<unknown, Error> {
|
||||
function resolve(ctx?: ExpressionLocalResolveContext): Result<unknown, Error> {
|
||||
const expressionString = toValue(expression);
|
||||
|
||||
if (!isExpression.value || typeof expressionString !== 'string') {
|
||||
return { ok: true, result: '' };
|
||||
}
|
||||
|
||||
let options: ResolveParameterOptions = {
|
||||
const options: ResolveParameterOptions | ExpressionLocalResolveContext = ctx ?? {
|
||||
isForCredential: toValue(isForCredential),
|
||||
additionalKeys: toValue(additionalData),
|
||||
contextNodeName: toValue(contextNodeName),
|
||||
...(contextNodeName === undefined && ndvStore.isInputParentOfActiveNode
|
||||
? {
|
||||
targetItem: targetItem.value ?? undefined,
|
||||
inputNodeName: ndvStore.ndvInputNodeName,
|
||||
inputRunIndex: ndvStore.ndvInputRunIndex,
|
||||
inputBranchIndex: ndvStore.ndvInputBranchIndex,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
if (contextNodeName === undefined && ndvStore.isInputParentOfActiveNode) {
|
||||
options = {
|
||||
...options,
|
||||
targetItem: targetItem.value ?? undefined,
|
||||
inputNodeName: ndvStore.ndvInputNodeName,
|
||||
inputRunIndex: ndvStore.ndvInputRunIndex,
|
||||
inputBranchIndex: ndvStore.ndvInputBranchIndex,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const resolvedValue = resolveExpression(
|
||||
expressionString,
|
||||
@@ -78,7 +92,7 @@ export function useResolvedExpression({
|
||||
|
||||
function updateExpression() {
|
||||
if (isExpression.value) {
|
||||
const resolved = resolve();
|
||||
const resolved = resolve(expressionLocalResolveCtx.value);
|
||||
resolvedExpression.value = resolved.ok ? resolved.result : null;
|
||||
resolvedExpressionString.value = stringifyExpressionResult(resolved, hasRunData.value);
|
||||
} else {
|
||||
@@ -89,6 +103,7 @@ export function useResolvedExpression({
|
||||
|
||||
watch(
|
||||
[
|
||||
expressionLocalResolveCtx,
|
||||
toRef(expression),
|
||||
() => workflowsStore.getWorkflowExecution,
|
||||
() => workflowsStore.getWorkflowRunData,
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
import type { IExecutionResponse, IWorkflowDb } from '@/Interface';
|
||||
import type { WorkflowData } from '@n8n/rest-api-client/api/workflows';
|
||||
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||
import { resolveParameter, useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import { setActivePinia } from 'pinia';
|
||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
|
||||
import { useTagsStore } from '@/stores/tags.store';
|
||||
import { useUIStore } from '@/stores/ui.store';
|
||||
import { createTestWorkflow } from '@/__tests__/mocks';
|
||||
import { WEBHOOK_NODE_TYPE, type AssignmentCollectionValue } from 'n8n-workflow';
|
||||
import {
|
||||
createTestNode,
|
||||
createTestTaskData,
|
||||
createTestWorkflow,
|
||||
createTestWorkflowExecutionResponse,
|
||||
createTestWorkflowObject,
|
||||
} from '@/__tests__/mocks';
|
||||
import {
|
||||
NodeConnectionTypes,
|
||||
WEBHOOK_NODE_TYPE,
|
||||
type AssignmentCollectionValue,
|
||||
} from 'n8n-workflow';
|
||||
import * as apiWebhooks from '@n8n/rest-api-client/api/webhooks';
|
||||
import { mockedStore } from '@/__tests__/utils';
|
||||
|
||||
@@ -871,3 +881,79 @@ describe('useWorkflowHelpers', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe(resolveParameter, () => {
|
||||
describe('with local resolve context', () => {
|
||||
it('should resolve parameter without execution data', () => {
|
||||
const result = resolveParameter(
|
||||
{
|
||||
f0: '={{ 2 + 2 }}',
|
||||
f1: '={{ $vars.foo }}',
|
||||
f2: '={{ String($exotic).toUpperCase() }}',
|
||||
},
|
||||
{
|
||||
localResolve: true,
|
||||
envVars: {
|
||||
foo: 'hello!',
|
||||
},
|
||||
additionalKeys: {
|
||||
$exotic: true,
|
||||
},
|
||||
workflow: createTestWorkflowObject({
|
||||
nodes: [createTestNode({ name: 'n0' })],
|
||||
}),
|
||||
execution: null,
|
||||
nodeName: 'n0',
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toEqual({ f0: 4, f1: 'hello!', f2: 'TRUE' });
|
||||
});
|
||||
|
||||
it('should resolve parameter with execution data', () => {
|
||||
const workflowData = createTestWorkflow({
|
||||
nodes: [createTestNode({ name: 'n0' }), createTestNode({ name: 'n1' })],
|
||||
connections: {
|
||||
n0: {
|
||||
[NodeConnectionTypes.Main]: [
|
||||
[{ type: NodeConnectionTypes.Main, index: 0, node: 'n1' }],
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
const result = resolveParameter(
|
||||
{
|
||||
f0: '={{ $json }}',
|
||||
f1: '={{ $("n0").item.json }}',
|
||||
},
|
||||
{
|
||||
localResolve: true,
|
||||
envVars: {},
|
||||
additionalKeys: {},
|
||||
workflow: createTestWorkflowObject(workflowData),
|
||||
execution: createTestWorkflowExecutionResponse({
|
||||
workflowData,
|
||||
data: {
|
||||
resultData: {
|
||||
runData: {
|
||||
n0: [
|
||||
createTestTaskData({
|
||||
data: { [NodeConnectionTypes.Main]: [[{ json: { foo: 777 } }]] },
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
nodeName: 'n1',
|
||||
inputNode: { name: 'n0', branchIndex: 0, runIndex: 0 },
|
||||
},
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
f0: { foo: 777 },
|
||||
f1: { foo: 777 },
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,8 @@ import type {
|
||||
INodeParameters,
|
||||
INodeProperties,
|
||||
INodeTypes,
|
||||
IPinData,
|
||||
IRunData,
|
||||
IRunExecutionData,
|
||||
IWebhookDescription,
|
||||
IWorkflowDataProxyAdditionalKeys,
|
||||
@@ -30,6 +32,7 @@ import {
|
||||
|
||||
import type {
|
||||
ICredentialsResponse,
|
||||
IExecutionResponse,
|
||||
INodeTypesMaxCount,
|
||||
INodeUi,
|
||||
IWorkflowDb,
|
||||
@@ -58,6 +61,7 @@ import { useProjectsStore } from '@/stores/projects.store';
|
||||
import { useTagsStore } from '@/stores/tags.store';
|
||||
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
|
||||
import { findWebhook } from '@n8n/rest-api-client/api/webhooks';
|
||||
import type { ExpressionLocalResolveContext } from '@/types/expressions';
|
||||
|
||||
export type ResolveParameterOptions = {
|
||||
targetItem?: TargetItem;
|
||||
@@ -71,11 +75,54 @@ export type ResolveParameterOptions = {
|
||||
|
||||
export function resolveParameter<T = IDataObject>(
|
||||
parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||
opts: ResolveParameterOptions | ExpressionLocalResolveContext = {},
|
||||
): T | null {
|
||||
if ('localResolve' in opts && opts.localResolve) {
|
||||
return resolveParameterImpl(
|
||||
parameter,
|
||||
() => opts.workflow,
|
||||
opts.envVars,
|
||||
opts.workflow.getNode(opts.nodeName),
|
||||
opts.execution,
|
||||
true,
|
||||
opts.workflow.pinData,
|
||||
{
|
||||
inputNodeName: opts.inputNode?.name,
|
||||
inputRunIndex: opts.inputNode?.runIndex,
|
||||
inputBranchIndex: opts.inputNode?.branchIndex,
|
||||
additionalKeys: opts.additionalKeys,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
|
||||
return resolveParameterImpl(
|
||||
parameter,
|
||||
workflowsStore.getCurrentWorkflow,
|
||||
useEnvironmentsStore().variablesAsObject,
|
||||
useNDVStore().activeNode,
|
||||
workflowsStore.workflowExecutionData,
|
||||
workflowsStore.shouldReplaceInputDataWithPinData,
|
||||
workflowsStore.pinnedWorkflowData,
|
||||
opts,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: move to separate file
|
||||
function resolveParameterImpl<T = IDataObject>(
|
||||
parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
||||
getContextWorkflow: () => Workflow,
|
||||
envVars: Record<string, string | boolean | number>,
|
||||
ndvActiveNode: INodeUi | null,
|
||||
executionData: IExecutionResponse | null,
|
||||
shouldReplaceInputDataWithPinData: boolean,
|
||||
pinData: IPinData | undefined,
|
||||
opts: ResolveParameterOptions = {},
|
||||
): T | null {
|
||||
let itemIndex = opts?.targetItem?.itemIndex || 0;
|
||||
|
||||
const workflow = getCurrentWorkflow();
|
||||
const workflow = getContextWorkflow();
|
||||
|
||||
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
|
||||
$execution: {
|
||||
@@ -84,7 +131,7 @@ export function resolveParameter<T = IDataObject>(
|
||||
resumeUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
resumeFormUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
},
|
||||
$vars: useEnvironmentsStore().variablesAsObject,
|
||||
$vars: envVars,
|
||||
|
||||
// deprecated
|
||||
$executionId: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||
@@ -113,17 +160,15 @@ export function resolveParameter<T = IDataObject>(
|
||||
|
||||
const inputName = NodeConnectionTypes.Main;
|
||||
|
||||
const activeNode =
|
||||
useNDVStore().activeNode ?? useWorkflowsStore().getNodeByName(opts.contextNodeName || '');
|
||||
const activeNode = ndvActiveNode ?? workflow.getNode(opts.contextNodeName || '');
|
||||
let contextNode = activeNode;
|
||||
|
||||
if (activeNode) {
|
||||
contextNode = workflow.getParentMainInputNode(activeNode);
|
||||
}
|
||||
|
||||
const workflowRunData = useWorkflowsStore().getWorkflowRunData;
|
||||
const workflowRunData = executionData?.data?.resultData.runData ?? null;
|
||||
let parentNode = workflow.getParentNodes(contextNode!.name, inputName, 1);
|
||||
const executionData = useWorkflowsStore().getWorkflowExecution;
|
||||
|
||||
let runIndexParent = opts?.inputRunIndex ?? 0;
|
||||
const nodeConnection = workflow.getNodeConnectionIndexes(contextNode!.name, parentNode[0]);
|
||||
@@ -159,13 +204,26 @@ export function resolveParameter<T = IDataObject>(
|
||||
contextNode!.name,
|
||||
inputName,
|
||||
runIndexParent,
|
||||
getContextWorkflow,
|
||||
shouldReplaceInputDataWithPinData,
|
||||
pinData,
|
||||
executionData?.data?.resultData.runData ?? null,
|
||||
nodeConnection,
|
||||
);
|
||||
|
||||
if (_connectionInputData === null && contextNode && activeNode?.name !== contextNode.name) {
|
||||
// For Sub-Nodes connected to Trigger-Nodes use the data of the root-node
|
||||
// (Gets for example used by the Memory connected to the Chat-Trigger-Node)
|
||||
const _executeData = executeData([contextNode.name], contextNode.name, inputName, 0);
|
||||
const _executeData = executeDataImpl(
|
||||
[contextNode.name],
|
||||
contextNode.name,
|
||||
inputName,
|
||||
0,
|
||||
getContextWorkflow,
|
||||
shouldReplaceInputDataWithPinData,
|
||||
pinData,
|
||||
executionData?.data?.resultData.runData ?? null,
|
||||
);
|
||||
_connectionInputData = get(_executeData, ['data', inputName, 0], null);
|
||||
}
|
||||
|
||||
@@ -206,17 +264,30 @@ export function resolveParameter<T = IDataObject>(
|
||||
) {
|
||||
runIndexCurrent = workflowRunData[contextNode!.name].length - 1;
|
||||
}
|
||||
let _executeData = executeData(
|
||||
let _executeData = executeDataImpl(
|
||||
parentNode,
|
||||
contextNode!.name,
|
||||
inputName,
|
||||
runIndexCurrent,
|
||||
getContextWorkflow,
|
||||
shouldReplaceInputDataWithPinData,
|
||||
pinData,
|
||||
executionData?.data?.resultData.runData ?? null,
|
||||
runIndexParent,
|
||||
);
|
||||
|
||||
if (!_executeData.source) {
|
||||
// fallback to parent's run index for multi-output case
|
||||
_executeData = executeData(parentNode, contextNode!.name, inputName, runIndexParent);
|
||||
_executeData = executeDataImpl(
|
||||
parentNode,
|
||||
contextNode!.name,
|
||||
inputName,
|
||||
runIndexParent,
|
||||
getContextWorkflow,
|
||||
shouldReplaceInputDataWithPinData,
|
||||
pinData,
|
||||
executionData?.data?.resultData.runData ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
return workflow.expression.getParameterValue(
|
||||
@@ -308,16 +379,30 @@ function getNodeTypes(): INodeTypes {
|
||||
return useWorkflowsStore().getNodeTypes();
|
||||
}
|
||||
|
||||
// TODO: move to separate file
|
||||
// Returns connectionInputData to be able to execute an expression.
|
||||
function connectionInputData(
|
||||
parentNode: string[],
|
||||
currentNode: string,
|
||||
inputName: string,
|
||||
runIndex: number,
|
||||
getContextWorkflow: () => Workflow,
|
||||
shouldReplaceInputDataWithPinData: boolean,
|
||||
pinData: IPinData | undefined,
|
||||
workflowRunData: IRunData | null,
|
||||
nodeConnection: INodeConnection = { sourceIndex: 0, destinationIndex: 0 },
|
||||
): INodeExecutionData[] | null {
|
||||
let connectionInputData: INodeExecutionData[] | null = null;
|
||||
const _executeData = executeData(parentNode, currentNode, inputName, runIndex);
|
||||
const _executeData = executeDataImpl(
|
||||
parentNode,
|
||||
currentNode,
|
||||
inputName,
|
||||
runIndex,
|
||||
getContextWorkflow,
|
||||
shouldReplaceInputDataWithPinData,
|
||||
pinData,
|
||||
workflowRunData,
|
||||
);
|
||||
if (parentNode.length) {
|
||||
if (
|
||||
!Object.keys(_executeData.data).length ||
|
||||
@@ -351,6 +436,33 @@ export function executeData(
|
||||
inputName: string,
|
||||
runIndex: number,
|
||||
parentRunIndex?: number,
|
||||
): IExecuteData {
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
|
||||
return executeDataImpl(
|
||||
parentNodes,
|
||||
currentNode,
|
||||
inputName,
|
||||
runIndex,
|
||||
workflowsStore.getCurrentWorkflow,
|
||||
workflowsStore.shouldReplaceInputDataWithPinData,
|
||||
workflowsStore.pinnedWorkflowData,
|
||||
workflowsStore.getWorkflowRunData,
|
||||
parentRunIndex,
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: move to separate file
|
||||
function executeDataImpl(
|
||||
parentNodes: string[],
|
||||
currentNode: string,
|
||||
inputName: string,
|
||||
runIndex: number,
|
||||
getContextWorkflow: () => Workflow,
|
||||
shouldReplaceInputDataWithPinData: boolean,
|
||||
pinData: IPinData | undefined,
|
||||
workflowRunData: IRunData | null,
|
||||
parentRunIndex?: number,
|
||||
): IExecuteData {
|
||||
const executeData = {
|
||||
node: {},
|
||||
@@ -360,12 +472,10 @@ export function executeData(
|
||||
|
||||
parentRunIndex = parentRunIndex ?? runIndex;
|
||||
|
||||
const workflowsStore = useWorkflowsStore();
|
||||
|
||||
// Find the parent node which has data
|
||||
for (const parentNodeName of parentNodes) {
|
||||
if (workflowsStore.shouldReplaceInputDataWithPinData) {
|
||||
const parentPinData = workflowsStore.pinnedWorkflowData![parentNodeName];
|
||||
if (shouldReplaceInputDataWithPinData) {
|
||||
const parentPinData = pinData?.[parentNodeName];
|
||||
|
||||
// populate `executeData` from `pinData`
|
||||
|
||||
@@ -378,7 +488,6 @@ export function executeData(
|
||||
}
|
||||
|
||||
// populate `executeData` from `runData`
|
||||
const workflowRunData = workflowsStore.getWorkflowRunData;
|
||||
if (workflowRunData === null) {
|
||||
return executeData;
|
||||
}
|
||||
@@ -398,7 +507,7 @@ export function executeData(
|
||||
[inputName]: workflowRunData[currentNode][runIndex].source,
|
||||
};
|
||||
} else {
|
||||
const workflow = getCurrentWorkflow();
|
||||
const workflow = getContextWorkflow();
|
||||
|
||||
let previousNodeOutput: number | undefined;
|
||||
// As the node can be connected through either of the outputs find the correct one
|
||||
@@ -739,7 +848,7 @@ export function useWorkflowHelpers() {
|
||||
function resolveExpression(
|
||||
expression: string,
|
||||
siblingParameters: INodeParameters = {},
|
||||
opts: ResolveParameterOptions & { c?: number } = {},
|
||||
opts: ResolveParameterOptions | ExpressionLocalResolveContext = {},
|
||||
stringifyObject = true,
|
||||
) {
|
||||
const parameters = {
|
||||
|
||||
@@ -10,7 +10,8 @@ import type {
|
||||
CanvasNodeHandleInjectionData,
|
||||
CanvasNodeInjectionData,
|
||||
} from '@/types';
|
||||
import type { InjectionKey, Ref } from 'vue';
|
||||
import type { ComputedRef, InjectionKey, Ref } from 'vue';
|
||||
import type { ExpressionLocalResolveContext } from './types/expressions';
|
||||
|
||||
export const MAX_WORKFLOW_SIZE = 1024 * 1024 * 16; // Workflow size limit in bytes
|
||||
export const MAX_EXPECTED_REQUEST_SIZE = 2048; // Expected maximum workflow request metadata (i.e. headers) size in bytes
|
||||
@@ -928,6 +929,9 @@ export const CanvasNodeKey = 'canvasNode' as unknown as InjectionKey<CanvasNodeI
|
||||
export const CanvasNodeHandleKey =
|
||||
'canvasNodeHandle' as unknown as InjectionKey<CanvasNodeHandleInjectionData>;
|
||||
export const PiPWindowSymbol = 'PiPWindow' as unknown as InjectionKey<Ref<Window | undefined>>;
|
||||
export const ExpressionLocalResolveContextSymbol = Symbol(
|
||||
'ExpressionLocalResolveContext',
|
||||
) as InjectionKey<ComputedRef<ExpressionLocalResolveContext | undefined>>;
|
||||
|
||||
/** Auth */
|
||||
export const APP_MODALS_ELEMENT_ID = 'app-modals';
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import type { Basic, IExecutionResponse } from '@/Interface';
|
||||
import type { IWorkflowDataProxyAdditionalKeys, Workflow } from 'n8n-workflow';
|
||||
|
||||
type Range = { from: number; to: number };
|
||||
|
||||
export type RawSegment = { text: string; token: string } & Range;
|
||||
@@ -27,3 +30,24 @@ export namespace ColoringStateEffect {
|
||||
state?: ResolvableState;
|
||||
} & Range;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collection of data, intended to be sufficient for resolving expressions
|
||||
* in parameter name/value without referencing global state
|
||||
*/
|
||||
export interface ExpressionLocalResolveContext {
|
||||
localResolve: true;
|
||||
envVars: Record<string, Basic>;
|
||||
additionalKeys: IWorkflowDataProxyAdditionalKeys;
|
||||
workflow: Workflow;
|
||||
execution: IExecutionResponse | null;
|
||||
nodeName: string;
|
||||
/**
|
||||
* Allowed to be undefined (e.g., trigger node, partial execution)
|
||||
*/
|
||||
inputNode?: {
|
||||
name: string;
|
||||
runIndex: number;
|
||||
branchIndex: number;
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user