mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(editor): Add visual-only waitingForNext execution state for slow networks (#16143)
This commit is contained in:
@@ -30,6 +30,7 @@ const {
|
|||||||
hasPinnedData,
|
hasPinnedData,
|
||||||
executionStatus,
|
executionStatus,
|
||||||
executionWaiting,
|
executionWaiting,
|
||||||
|
executionWaitingForNext,
|
||||||
executionRunning,
|
executionRunning,
|
||||||
hasRunData,
|
hasRunData,
|
||||||
hasIssues,
|
hasIssues,
|
||||||
@@ -61,7 +62,7 @@ const classes = computed(() => {
|
|||||||
[$style.error]: hasIssues.value,
|
[$style.error]: hasIssues.value,
|
||||||
[$style.pinned]: hasPinnedData.value,
|
[$style.pinned]: hasPinnedData.value,
|
||||||
[$style.waiting]: executionWaiting.value ?? executionStatus.value === 'waiting',
|
[$style.waiting]: executionWaiting.value ?? executionStatus.value === 'waiting',
|
||||||
[$style.running]: executionRunning.value,
|
[$style.running]: executionRunning.value || executionWaitingForNext.value,
|
||||||
[$style.configurable]: renderOptions.value.configurable,
|
[$style.configurable]: renderOptions.value.configurable,
|
||||||
[$style.configuration]: renderOptions.value.configuration,
|
[$style.configuration]: renderOptions.value.configuration,
|
||||||
[$style.trigger]: renderOptions.value.trigger,
|
[$style.trigger]: renderOptions.value.trigger,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const {
|
|||||||
hasIssues,
|
hasIssues,
|
||||||
executionStatus,
|
executionStatus,
|
||||||
executionWaiting,
|
executionWaiting,
|
||||||
|
executionWaitingForNext,
|
||||||
executionRunning,
|
executionRunning,
|
||||||
hasRunData,
|
hasRunData,
|
||||||
runDataIterations,
|
runDataIterations,
|
||||||
@@ -59,7 +60,7 @@ const dirtiness = computed(() =>
|
|||||||
<!-- Do nothing, unknown means the node never executed -->
|
<!-- Do nothing, unknown means the node never executed -->
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="executionRunning || executionStatus === 'running'"
|
v-else-if="executionRunning || executionWaitingForNext || executionStatus === 'running'"
|
||||||
data-test-id="canvas-node-status-running"
|
data-test-id="canvas-node-status-running"
|
||||||
:class="[$style.status, $style.running]"
|
:class="[$style.status, $style.running]"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ describe('useCanvasMapping', () => {
|
|||||||
status: 'new',
|
status: 'new',
|
||||||
running: false,
|
running: false,
|
||||||
waiting: undefined,
|
waiting: undefined,
|
||||||
|
waitingForNext: false,
|
||||||
},
|
},
|
||||||
issues: {
|
issues: {
|
||||||
items: [],
|
items: [],
|
||||||
@@ -1202,6 +1203,98 @@ describe('useCanvasMapping', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('nodeExecutionWaitingForNextById', () => {
|
||||||
|
it('should be true when already executed node is waiting for next', () => {
|
||||||
|
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||||
|
const node1 = createTestNode({
|
||||||
|
name: 'Node 1',
|
||||||
|
});
|
||||||
|
const node2 = createTestNode({
|
||||||
|
name: 'Node 2',
|
||||||
|
});
|
||||||
|
const nodes = [node1, node2];
|
||||||
|
const connections = {};
|
||||||
|
|
||||||
|
const workflowObject = createTestWorkflowObject({
|
||||||
|
nodes,
|
||||||
|
connections,
|
||||||
|
});
|
||||||
|
|
||||||
|
workflowsStore.executingNode = [];
|
||||||
|
workflowsStore.lastAddedExecutingNode = node1.name;
|
||||||
|
workflowsStore.isWorkflowRunning = true;
|
||||||
|
|
||||||
|
const { nodeExecutionWaitingForNextById } = useCanvasMapping({
|
||||||
|
nodes: ref(nodes),
|
||||||
|
connections: ref(connections),
|
||||||
|
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(nodeExecutionWaitingForNextById.value[node1.id]).toBe(true);
|
||||||
|
expect(nodeExecutionWaitingForNextById.value[node2.id]).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be false when workflow is not executing', () => {
|
||||||
|
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||||
|
const node1 = createTestNode({
|
||||||
|
name: 'Node 1',
|
||||||
|
});
|
||||||
|
const node2 = createTestNode({
|
||||||
|
name: 'Node 2',
|
||||||
|
});
|
||||||
|
const nodes = [node1, node2];
|
||||||
|
const connections = {};
|
||||||
|
|
||||||
|
const workflowObject = createTestWorkflowObject({
|
||||||
|
nodes,
|
||||||
|
connections,
|
||||||
|
});
|
||||||
|
|
||||||
|
workflowsStore.executingNode = [];
|
||||||
|
workflowsStore.lastAddedExecutingNode = node1.name;
|
||||||
|
workflowsStore.isWorkflowRunning = false;
|
||||||
|
|
||||||
|
const { nodeExecutionWaitingForNextById } = useCanvasMapping({
|
||||||
|
nodes: ref(nodes),
|
||||||
|
connections: ref(connections),
|
||||||
|
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(nodeExecutionWaitingForNextById.value[node1.id]).toBe(false);
|
||||||
|
expect(nodeExecutionWaitingForNextById.value[node2.id]).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be false when there are nodes that are executing', () => {
|
||||||
|
const workflowsStore = mockedStore(useWorkflowsStore);
|
||||||
|
const node1 = createTestNode({
|
||||||
|
name: 'Node 1',
|
||||||
|
});
|
||||||
|
const node2 = createTestNode({
|
||||||
|
name: 'Node 2',
|
||||||
|
});
|
||||||
|
const nodes = [node1, node2];
|
||||||
|
const connections = {};
|
||||||
|
|
||||||
|
const workflowObject = createTestWorkflowObject({
|
||||||
|
nodes,
|
||||||
|
connections,
|
||||||
|
});
|
||||||
|
|
||||||
|
workflowsStore.executingNode = [node2.name];
|
||||||
|
workflowsStore.lastAddedExecutingNode = node1.name;
|
||||||
|
workflowsStore.isWorkflowRunning = false;
|
||||||
|
|
||||||
|
const { nodeExecutionWaitingForNextById } = useCanvasMapping({
|
||||||
|
nodes: ref(nodes),
|
||||||
|
connections: ref(connections),
|
||||||
|
workflowObject: ref(workflowObject) as Ref<Workflow>,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(nodeExecutionWaitingForNextById.value[node1.id]).toBe(false);
|
||||||
|
expect(nodeExecutionWaitingForNextById.value[node2.id]).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('connections', () => {
|
describe('connections', () => {
|
||||||
it('should map connections to canvas connections', () => {
|
it('should map connections to canvas connections', () => {
|
||||||
const [manualTriggerNode, setNode] = mockNodes.slice(0, 2);
|
const [manualTriggerNode, setNode] = mockNodes.slice(0, 2);
|
||||||
|
|||||||
@@ -327,6 +327,17 @@ export function useCanvasMapping({
|
|||||||
}, {}),
|
}, {}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const nodeExecutionWaitingForNextById = computed(() =>
|
||||||
|
nodes.value.reduce<Record<string, boolean>>((acc, node) => {
|
||||||
|
acc[node.id] =
|
||||||
|
node.name === workflowsStore.lastAddedExecutingNode &&
|
||||||
|
workflowsStore.executingNode.length === 0 &&
|
||||||
|
workflowsStore.isWorkflowRunning;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {}),
|
||||||
|
);
|
||||||
|
|
||||||
const nodeExecutionStatusById = computed(() =>
|
const nodeExecutionStatusById = computed(() =>
|
||||||
nodes.value.reduce<Record<string, ExecutionStatus>>((acc, node) => {
|
nodes.value.reduce<Record<string, ExecutionStatus>>((acc, node) => {
|
||||||
const tasks = workflowsStore.getWorkflowRunData?.[node.name] ?? [];
|
const tasks = workflowsStore.getWorkflowRunData?.[node.name] ?? [];
|
||||||
@@ -589,6 +600,7 @@ export function useCanvasMapping({
|
|||||||
execution: {
|
execution: {
|
||||||
status: nodeExecutionStatusById.value[node.id],
|
status: nodeExecutionStatusById.value[node.id],
|
||||||
waiting: nodeExecutionWaitingById.value[node.id],
|
waiting: nodeExecutionWaitingById.value[node.id],
|
||||||
|
waitingForNext: nodeExecutionWaitingForNextById.value[node.id],
|
||||||
running: nodeExecutionRunningById.value[node.id],
|
running: nodeExecutionRunningById.value[node.id],
|
||||||
},
|
},
|
||||||
runData: {
|
runData: {
|
||||||
@@ -704,6 +716,7 @@ export function useCanvasMapping({
|
|||||||
return {
|
return {
|
||||||
additionalNodePropertiesById,
|
additionalNodePropertiesById,
|
||||||
nodeExecutionRunDataOutputMapById,
|
nodeExecutionRunDataOutputMapById,
|
||||||
|
nodeExecutionWaitingForNextById,
|
||||||
nodeIssuesById,
|
nodeIssuesById,
|
||||||
nodeHasIssuesById,
|
nodeHasIssuesById,
|
||||||
connections: mappedConnections,
|
connections: mappedConnections,
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export function useCanvasNode() {
|
|||||||
|
|
||||||
const executionStatus = computed(() => data.value.execution.status);
|
const executionStatus = computed(() => data.value.execution.status);
|
||||||
const executionWaiting = computed(() => data.value.execution.waiting);
|
const executionWaiting = computed(() => data.value.execution.waiting);
|
||||||
|
const executionWaitingForNext = computed(() => data.value.execution.waitingForNext);
|
||||||
const executionRunning = computed(() => data.value.execution.running);
|
const executionRunning = computed(() => data.value.execution.running);
|
||||||
|
|
||||||
const runDataOutputMap = computed(() => data.value.runData.outputMap);
|
const runDataOutputMap = computed(() => data.value.runData.outputMap);
|
||||||
@@ -83,6 +84,7 @@ export function useCanvasNode() {
|
|||||||
hasIssues,
|
hasIssues,
|
||||||
executionStatus,
|
executionStatus,
|
||||||
executionWaiting,
|
executionWaiting,
|
||||||
|
executionWaitingForNext,
|
||||||
executionRunning,
|
executionRunning,
|
||||||
render,
|
render,
|
||||||
eventBus,
|
eventBus,
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ import { ref } from 'vue';
|
|||||||
*/
|
*/
|
||||||
export function useExecutingNode() {
|
export function useExecutingNode() {
|
||||||
const executingNode = ref<string[]>([]);
|
const executingNode = ref<string[]>([]);
|
||||||
|
const lastAddedExecutingNode = ref<string | null>(null);
|
||||||
|
|
||||||
function addExecutingNode(nodeName: string) {
|
function addExecutingNode(nodeName: string) {
|
||||||
executingNode.value.push(nodeName);
|
executingNode.value.push(nodeName);
|
||||||
|
lastAddedExecutingNode.value = nodeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeExecutingNode(nodeName: string) {
|
function removeExecutingNode(nodeName: string) {
|
||||||
@@ -30,6 +32,7 @@ export function useExecutingNode() {
|
|||||||
|
|
||||||
function clearNodeExecutionQueue() {
|
function clearNodeExecutionQueue() {
|
||||||
executingNode.value = [];
|
executingNode.value = [];
|
||||||
|
lastAddedExecutingNode.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNodeExecuting(nodeName: string): boolean {
|
function isNodeExecuting(nodeName: string): boolean {
|
||||||
@@ -38,6 +41,7 @@ export function useExecutingNode() {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
executingNode,
|
executingNode,
|
||||||
|
lastAddedExecutingNode,
|
||||||
addExecutingNode,
|
addExecutingNode,
|
||||||
removeExecutingNode,
|
removeExecutingNode,
|
||||||
isNodeExecuting,
|
isNodeExecuting,
|
||||||
|
|||||||
@@ -27,12 +27,7 @@ export async function nodeExecuteAfter({ data: pushData }: NodeExecuteAfter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
workflowsStore.updateNodeExecutionData(pushData);
|
workflowsStore.updateNodeExecutionData(pushData);
|
||||||
|
workflowsStore.removeExecutingNode(pushData.nodeName);
|
||||||
// Remove the node from the executing queue after a short delay
|
|
||||||
// To allow the running spinner to show for at least 50ms
|
|
||||||
setTimeout(() => {
|
|
||||||
workflowsStore.removeExecutingNode(pushData.nodeName);
|
|
||||||
}, 50);
|
|
||||||
|
|
||||||
void assistantStore.onNodeExecution(pushData);
|
void assistantStore.onNodeExecution(pushData);
|
||||||
void schemaPreviewStore.trackSchemaPreviewExecution(pushData);
|
void schemaPreviewStore.trackSchemaPreviewExecution(pushData);
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
executingNode,
|
executingNode,
|
||||||
|
lastAddedExecutingNode,
|
||||||
addExecutingNode,
|
addExecutingNode,
|
||||||
removeExecutingNode,
|
removeExecutingNode,
|
||||||
isNodeExecuting,
|
isNodeExecuting,
|
||||||
@@ -1938,6 +1939,7 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
|
|||||||
subWorkflowExecutionError,
|
subWorkflowExecutionError,
|
||||||
executionWaitingForWebhook,
|
executionWaitingForWebhook,
|
||||||
executingNode,
|
executingNode,
|
||||||
|
lastAddedExecutingNode,
|
||||||
workflowsById,
|
workflowsById,
|
||||||
nodeMetadata,
|
nodeMetadata,
|
||||||
isInDebugMode,
|
isInDebugMode,
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ export interface CanvasNodeData {
|
|||||||
status?: ExecutionStatus;
|
status?: ExecutionStatus;
|
||||||
waiting?: string;
|
waiting?: string;
|
||||||
running: boolean;
|
running: boolean;
|
||||||
|
waitingForNext?: boolean;
|
||||||
};
|
};
|
||||||
runData: {
|
runData: {
|
||||||
outputMap: ExecutionOutputMap;
|
outputMap: ExecutionOutputMap;
|
||||||
|
|||||||
Reference in New Issue
Block a user