mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +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">
|
<script setup lang="ts">
|
||||||
import ExperimentalCanvasNodeSettings from './ExperimentalCanvasNodeSettings.vue';
|
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 { useNodeTypesStore } from '@/stores/nodeTypes.store';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useExperimentalNdvStore } from '../experimentalNdv.store';
|
import { useExperimentalNdvStore } from '../experimentalNdv.store';
|
||||||
@@ -8,6 +8,9 @@ import NodeTitle from '@/components/NodeTitle.vue';
|
|||||||
import { N8nIcon, N8nIconButton } from '@n8n/design-system';
|
import { N8nIcon, N8nIconButton } from '@n8n/design-system';
|
||||||
import { useVueFlow } from '@vue-flow/core';
|
import { useVueFlow } from '@vue-flow/core';
|
||||||
import { watchOnce } from '@vueuse/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<{
|
const { nodeId, isReadOnly, isConfigurable } = defineProps<{
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@@ -56,6 +59,55 @@ const isVisible = computed(() =>
|
|||||||
);
|
);
|
||||||
const isOnceVisible = ref(isVisible.value);
|
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) => {
|
watchOnce(isVisible, (visible) => {
|
||||||
isOnceVisible.value = isOnceVisible.value || visible;
|
isOnceVisible.value = isOnceVisible.value || visible;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
computed,
|
computed,
|
||||||
|
inject,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
onMounted,
|
onMounted,
|
||||||
ref,
|
ref,
|
||||||
@@ -15,7 +16,7 @@ import { ensureSyntaxTree } from '@codemirror/language';
|
|||||||
import type { IDataObject } from 'n8n-workflow';
|
import type { IDataObject } from 'n8n-workflow';
|
||||||
import { Expression, ExpressionExtensions } 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 { useNDVStore } from '@/stores/ndv.store';
|
||||||
|
|
||||||
import type { TargetItem, TargetNodeParameterContext } from '@/Interface';
|
import type { TargetItem, TargetNodeParameterContext } from '@/Interface';
|
||||||
@@ -75,6 +76,10 @@ export const useExpressionEditor = ({
|
|||||||
const autocompleteStatus = ref<'pending' | 'active' | null>(null);
|
const autocompleteStatus = ref<'pending' | 'active' | null>(null);
|
||||||
const dragging = ref(false);
|
const dragging = ref(false);
|
||||||
const hasChanges = ref(false);
|
const hasChanges = ref(false);
|
||||||
|
const expressionLocalResolveContext = inject(
|
||||||
|
ExpressionLocalResolveContextSymbol,
|
||||||
|
computed(() => undefined),
|
||||||
|
);
|
||||||
|
|
||||||
const emitChanges = debounce(onChange, 300);
|
const emitChanges = debounce(onChange, 300);
|
||||||
|
|
||||||
@@ -307,7 +312,12 @@ export const useExpressionEditor = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
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
|
// e.g. credential modal
|
||||||
result.resolved = Expression.resolveWithoutWorkflow(resolvable, toValue(additionalData));
|
result.resolved = Expression.resolveWithoutWorkflow(resolvable, toValue(additionalData));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,8 +4,19 @@ import { isExpression as isExpressionUtil, stringifyExpressionResult } from '@/u
|
|||||||
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { createResultError, createResultOk, type IDataObject, type Result } from 'n8n-workflow';
|
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 { useWorkflowHelpers, type ResolveParameterOptions } from './useWorkflowHelpers';
|
||||||
|
import { ExpressionLocalResolveContextSymbol } from '@/constants';
|
||||||
|
import type { ExpressionLocalResolveContext } from '@/types/expressions';
|
||||||
|
|
||||||
export function useResolvedExpression({
|
export function useResolvedExpression({
|
||||||
expression,
|
expression,
|
||||||
@@ -25,6 +36,11 @@ export function useResolvedExpression({
|
|||||||
|
|
||||||
const { resolveExpression } = useWorkflowHelpers();
|
const { resolveExpression } = useWorkflowHelpers();
|
||||||
|
|
||||||
|
const expressionLocalResolveCtx = inject(
|
||||||
|
ExpressionLocalResolveContextSymbol,
|
||||||
|
computed(() => undefined),
|
||||||
|
);
|
||||||
|
|
||||||
const resolvedExpression = ref<unknown>(null);
|
const resolvedExpression = ref<unknown>(null);
|
||||||
const resolvedExpressionString = ref('');
|
const resolvedExpressionString = ref('');
|
||||||
|
|
||||||
@@ -37,29 +53,27 @@ export function useResolvedExpression({
|
|||||||
);
|
);
|
||||||
const isExpression = computed(() => isExpressionUtil(toValue(expression)));
|
const isExpression = computed(() => isExpressionUtil(toValue(expression)));
|
||||||
|
|
||||||
function resolve(): Result<unknown, Error> {
|
function resolve(ctx?: ExpressionLocalResolveContext): Result<unknown, Error> {
|
||||||
const expressionString = toValue(expression);
|
const expressionString = toValue(expression);
|
||||||
|
|
||||||
if (!isExpression.value || typeof expressionString !== 'string') {
|
if (!isExpression.value || typeof expressionString !== 'string') {
|
||||||
return { ok: true, result: '' };
|
return { ok: true, result: '' };
|
||||||
}
|
}
|
||||||
|
|
||||||
let options: ResolveParameterOptions = {
|
const options: ResolveParameterOptions | ExpressionLocalResolveContext = ctx ?? {
|
||||||
isForCredential: toValue(isForCredential),
|
isForCredential: toValue(isForCredential),
|
||||||
additionalKeys: toValue(additionalData),
|
additionalKeys: toValue(additionalData),
|
||||||
contextNodeName: toValue(contextNodeName),
|
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 {
|
try {
|
||||||
const resolvedValue = resolveExpression(
|
const resolvedValue = resolveExpression(
|
||||||
expressionString,
|
expressionString,
|
||||||
@@ -78,7 +92,7 @@ export function useResolvedExpression({
|
|||||||
|
|
||||||
function updateExpression() {
|
function updateExpression() {
|
||||||
if (isExpression.value) {
|
if (isExpression.value) {
|
||||||
const resolved = resolve();
|
const resolved = resolve(expressionLocalResolveCtx.value);
|
||||||
resolvedExpression.value = resolved.ok ? resolved.result : null;
|
resolvedExpression.value = resolved.ok ? resolved.result : null;
|
||||||
resolvedExpressionString.value = stringifyExpressionResult(resolved, hasRunData.value);
|
resolvedExpressionString.value = stringifyExpressionResult(resolved, hasRunData.value);
|
||||||
} else {
|
} else {
|
||||||
@@ -89,6 +103,7 @@ export function useResolvedExpression({
|
|||||||
|
|
||||||
watch(
|
watch(
|
||||||
[
|
[
|
||||||
|
expressionLocalResolveCtx,
|
||||||
toRef(expression),
|
toRef(expression),
|
||||||
() => workflowsStore.getWorkflowExecution,
|
() => workflowsStore.getWorkflowExecution,
|
||||||
() => workflowsStore.getWorkflowRunData,
|
() => workflowsStore.getWorkflowRunData,
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
import type { IExecutionResponse, IWorkflowDb } from '@/Interface';
|
import type { IExecutionResponse, IWorkflowDb } from '@/Interface';
|
||||||
import type { WorkflowData } from '@n8n/rest-api-client/api/workflows';
|
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 { createTestingPinia } from '@pinia/testing';
|
||||||
import { setActivePinia } from 'pinia';
|
import { setActivePinia } from 'pinia';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
|
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
|
||||||
import { useTagsStore } from '@/stores/tags.store';
|
import { useTagsStore } from '@/stores/tags.store';
|
||||||
import { useUIStore } from '@/stores/ui.store';
|
import { useUIStore } from '@/stores/ui.store';
|
||||||
import { createTestWorkflow } from '@/__tests__/mocks';
|
import {
|
||||||
import { WEBHOOK_NODE_TYPE, type AssignmentCollectionValue } from 'n8n-workflow';
|
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 * as apiWebhooks from '@n8n/rest-api-client/api/webhooks';
|
||||||
import { mockedStore } from '@/__tests__/utils';
|
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,
|
INodeParameters,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
INodeTypes,
|
INodeTypes,
|
||||||
|
IPinData,
|
||||||
|
IRunData,
|
||||||
IRunExecutionData,
|
IRunExecutionData,
|
||||||
IWebhookDescription,
|
IWebhookDescription,
|
||||||
IWorkflowDataProxyAdditionalKeys,
|
IWorkflowDataProxyAdditionalKeys,
|
||||||
@@ -30,6 +32,7 @@ import {
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ICredentialsResponse,
|
ICredentialsResponse,
|
||||||
|
IExecutionResponse,
|
||||||
INodeTypesMaxCount,
|
INodeTypesMaxCount,
|
||||||
INodeUi,
|
INodeUi,
|
||||||
IWorkflowDb,
|
IWorkflowDb,
|
||||||
@@ -58,6 +61,7 @@ import { useProjectsStore } from '@/stores/projects.store';
|
|||||||
import { useTagsStore } from '@/stores/tags.store';
|
import { useTagsStore } from '@/stores/tags.store';
|
||||||
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
|
import { useWorkflowsEEStore } from '@/stores/workflows.ee.store';
|
||||||
import { findWebhook } from '@n8n/rest-api-client/api/webhooks';
|
import { findWebhook } from '@n8n/rest-api-client/api/webhooks';
|
||||||
|
import type { ExpressionLocalResolveContext } from '@/types/expressions';
|
||||||
|
|
||||||
export type ResolveParameterOptions = {
|
export type ResolveParameterOptions = {
|
||||||
targetItem?: TargetItem;
|
targetItem?: TargetItem;
|
||||||
@@ -71,11 +75,54 @@ export type ResolveParameterOptions = {
|
|||||||
|
|
||||||
export function resolveParameter<T = IDataObject>(
|
export function resolveParameter<T = IDataObject>(
|
||||||
parameter: NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[],
|
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 = {},
|
opts: ResolveParameterOptions = {},
|
||||||
): T | null {
|
): T | null {
|
||||||
let itemIndex = opts?.targetItem?.itemIndex || 0;
|
let itemIndex = opts?.targetItem?.itemIndex || 0;
|
||||||
|
|
||||||
const workflow = getCurrentWorkflow();
|
const workflow = getContextWorkflow();
|
||||||
|
|
||||||
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
|
const additionalKeys: IWorkflowDataProxyAdditionalKeys = {
|
||||||
$execution: {
|
$execution: {
|
||||||
@@ -84,7 +131,7 @@ export function resolveParameter<T = IDataObject>(
|
|||||||
resumeUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
resumeUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||||
resumeFormUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
resumeFormUrl: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||||
},
|
},
|
||||||
$vars: useEnvironmentsStore().variablesAsObject,
|
$vars: envVars,
|
||||||
|
|
||||||
// deprecated
|
// deprecated
|
||||||
$executionId: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
$executionId: PLACEHOLDER_FILLED_AT_EXECUTION_TIME,
|
||||||
@@ -113,17 +160,15 @@ export function resolveParameter<T = IDataObject>(
|
|||||||
|
|
||||||
const inputName = NodeConnectionTypes.Main;
|
const inputName = NodeConnectionTypes.Main;
|
||||||
|
|
||||||
const activeNode =
|
const activeNode = ndvActiveNode ?? workflow.getNode(opts.contextNodeName || '');
|
||||||
useNDVStore().activeNode ?? useWorkflowsStore().getNodeByName(opts.contextNodeName || '');
|
|
||||||
let contextNode = activeNode;
|
let contextNode = activeNode;
|
||||||
|
|
||||||
if (activeNode) {
|
if (activeNode) {
|
||||||
contextNode = workflow.getParentMainInputNode(activeNode);
|
contextNode = workflow.getParentMainInputNode(activeNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowRunData = useWorkflowsStore().getWorkflowRunData;
|
const workflowRunData = executionData?.data?.resultData.runData ?? null;
|
||||||
let parentNode = workflow.getParentNodes(contextNode!.name, inputName, 1);
|
let parentNode = workflow.getParentNodes(contextNode!.name, inputName, 1);
|
||||||
const executionData = useWorkflowsStore().getWorkflowExecution;
|
|
||||||
|
|
||||||
let runIndexParent = opts?.inputRunIndex ?? 0;
|
let runIndexParent = opts?.inputRunIndex ?? 0;
|
||||||
const nodeConnection = workflow.getNodeConnectionIndexes(contextNode!.name, parentNode[0]);
|
const nodeConnection = workflow.getNodeConnectionIndexes(contextNode!.name, parentNode[0]);
|
||||||
@@ -159,13 +204,26 @@ export function resolveParameter<T = IDataObject>(
|
|||||||
contextNode!.name,
|
contextNode!.name,
|
||||||
inputName,
|
inputName,
|
||||||
runIndexParent,
|
runIndexParent,
|
||||||
|
getContextWorkflow,
|
||||||
|
shouldReplaceInputDataWithPinData,
|
||||||
|
pinData,
|
||||||
|
executionData?.data?.resultData.runData ?? null,
|
||||||
nodeConnection,
|
nodeConnection,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (_connectionInputData === null && contextNode && activeNode?.name !== contextNode.name) {
|
if (_connectionInputData === null && contextNode && activeNode?.name !== contextNode.name) {
|
||||||
// For Sub-Nodes connected to Trigger-Nodes use the data of the root-node
|
// 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)
|
// (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);
|
_connectionInputData = get(_executeData, ['data', inputName, 0], null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,17 +264,30 @@ export function resolveParameter<T = IDataObject>(
|
|||||||
) {
|
) {
|
||||||
runIndexCurrent = workflowRunData[contextNode!.name].length - 1;
|
runIndexCurrent = workflowRunData[contextNode!.name].length - 1;
|
||||||
}
|
}
|
||||||
let _executeData = executeData(
|
let _executeData = executeDataImpl(
|
||||||
parentNode,
|
parentNode,
|
||||||
contextNode!.name,
|
contextNode!.name,
|
||||||
inputName,
|
inputName,
|
||||||
runIndexCurrent,
|
runIndexCurrent,
|
||||||
|
getContextWorkflow,
|
||||||
|
shouldReplaceInputDataWithPinData,
|
||||||
|
pinData,
|
||||||
|
executionData?.data?.resultData.runData ?? null,
|
||||||
runIndexParent,
|
runIndexParent,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!_executeData.source) {
|
if (!_executeData.source) {
|
||||||
// fallback to parent's run index for multi-output case
|
// 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(
|
return workflow.expression.getParameterValue(
|
||||||
@@ -308,16 +379,30 @@ function getNodeTypes(): INodeTypes {
|
|||||||
return useWorkflowsStore().getNodeTypes();
|
return useWorkflowsStore().getNodeTypes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: move to separate file
|
||||||
// Returns connectionInputData to be able to execute an expression.
|
// Returns connectionInputData to be able to execute an expression.
|
||||||
function connectionInputData(
|
function connectionInputData(
|
||||||
parentNode: string[],
|
parentNode: string[],
|
||||||
currentNode: string,
|
currentNode: string,
|
||||||
inputName: string,
|
inputName: string,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
|
getContextWorkflow: () => Workflow,
|
||||||
|
shouldReplaceInputDataWithPinData: boolean,
|
||||||
|
pinData: IPinData | undefined,
|
||||||
|
workflowRunData: IRunData | null,
|
||||||
nodeConnection: INodeConnection = { sourceIndex: 0, destinationIndex: 0 },
|
nodeConnection: INodeConnection = { sourceIndex: 0, destinationIndex: 0 },
|
||||||
): INodeExecutionData[] | null {
|
): INodeExecutionData[] | null {
|
||||||
let connectionInputData: INodeExecutionData[] | null = 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 (parentNode.length) {
|
||||||
if (
|
if (
|
||||||
!Object.keys(_executeData.data).length ||
|
!Object.keys(_executeData.data).length ||
|
||||||
@@ -351,6 +436,33 @@ export function executeData(
|
|||||||
inputName: string,
|
inputName: string,
|
||||||
runIndex: number,
|
runIndex: number,
|
||||||
parentRunIndex?: 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 {
|
): IExecuteData {
|
||||||
const executeData = {
|
const executeData = {
|
||||||
node: {},
|
node: {},
|
||||||
@@ -360,12 +472,10 @@ export function executeData(
|
|||||||
|
|
||||||
parentRunIndex = parentRunIndex ?? runIndex;
|
parentRunIndex = parentRunIndex ?? runIndex;
|
||||||
|
|
||||||
const workflowsStore = useWorkflowsStore();
|
|
||||||
|
|
||||||
// Find the parent node which has data
|
// Find the parent node which has data
|
||||||
for (const parentNodeName of parentNodes) {
|
for (const parentNodeName of parentNodes) {
|
||||||
if (workflowsStore.shouldReplaceInputDataWithPinData) {
|
if (shouldReplaceInputDataWithPinData) {
|
||||||
const parentPinData = workflowsStore.pinnedWorkflowData![parentNodeName];
|
const parentPinData = pinData?.[parentNodeName];
|
||||||
|
|
||||||
// populate `executeData` from `pinData`
|
// populate `executeData` from `pinData`
|
||||||
|
|
||||||
@@ -378,7 +488,6 @@ export function executeData(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// populate `executeData` from `runData`
|
// populate `executeData` from `runData`
|
||||||
const workflowRunData = workflowsStore.getWorkflowRunData;
|
|
||||||
if (workflowRunData === null) {
|
if (workflowRunData === null) {
|
||||||
return executeData;
|
return executeData;
|
||||||
}
|
}
|
||||||
@@ -398,7 +507,7 @@ export function executeData(
|
|||||||
[inputName]: workflowRunData[currentNode][runIndex].source,
|
[inputName]: workflowRunData[currentNode][runIndex].source,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
const workflow = getCurrentWorkflow();
|
const workflow = getContextWorkflow();
|
||||||
|
|
||||||
let previousNodeOutput: number | undefined;
|
let previousNodeOutput: number | undefined;
|
||||||
// As the node can be connected through either of the outputs find the correct one
|
// As the node can be connected through either of the outputs find the correct one
|
||||||
@@ -739,7 +848,7 @@ export function useWorkflowHelpers() {
|
|||||||
function resolveExpression(
|
function resolveExpression(
|
||||||
expression: string,
|
expression: string,
|
||||||
siblingParameters: INodeParameters = {},
|
siblingParameters: INodeParameters = {},
|
||||||
opts: ResolveParameterOptions & { c?: number } = {},
|
opts: ResolveParameterOptions | ExpressionLocalResolveContext = {},
|
||||||
stringifyObject = true,
|
stringifyObject = true,
|
||||||
) {
|
) {
|
||||||
const parameters = {
|
const parameters = {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import type {
|
|||||||
CanvasNodeHandleInjectionData,
|
CanvasNodeHandleInjectionData,
|
||||||
CanvasNodeInjectionData,
|
CanvasNodeInjectionData,
|
||||||
} from '@/types';
|
} 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_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
|
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 =
|
export const CanvasNodeHandleKey =
|
||||||
'canvasNodeHandle' as unknown as InjectionKey<CanvasNodeHandleInjectionData>;
|
'canvasNodeHandle' as unknown as InjectionKey<CanvasNodeHandleInjectionData>;
|
||||||
export const PiPWindowSymbol = 'PiPWindow' as unknown as InjectionKey<Ref<Window | undefined>>;
|
export const PiPWindowSymbol = 'PiPWindow' as unknown as InjectionKey<Ref<Window | undefined>>;
|
||||||
|
export const ExpressionLocalResolveContextSymbol = Symbol(
|
||||||
|
'ExpressionLocalResolveContext',
|
||||||
|
) as InjectionKey<ComputedRef<ExpressionLocalResolveContext | undefined>>;
|
||||||
|
|
||||||
/** Auth */
|
/** Auth */
|
||||||
export const APP_MODALS_ELEMENT_ID = 'app-modals';
|
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 };
|
type Range = { from: number; to: number };
|
||||||
|
|
||||||
export type RawSegment = { text: string; token: string } & Range;
|
export type RawSegment = { text: string; token: string } & Range;
|
||||||
@@ -27,3 +30,24 @@ export namespace ColoringStateEffect {
|
|||||||
state?: ResolvableState;
|
state?: ResolvableState;
|
||||||
} & Range;
|
} & 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