Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { waitFor } from '@testing-library/vue';
import { createComponentRenderer } from '@/__tests__/render';
import { type MockedStore, mockedStore, getTooltip } from '@/__tests__/utils';
import { mockNode, mockNodeTypeDescription } from '@/__tests__/mocks';
import { nodeViewEventBus } from '@/app/event-bus';
import {
AI_TRANSFORM_NODE_TYPE,
AI_TRANSFORM_CODE_GENERATED_FOR_PROMPT,
Expand Down Expand Up @@ -122,8 +121,6 @@ let message: ReturnType<typeof useMessage>;
let toast: ReturnType<typeof useToast>;
let workflowState: WorkflowState;

const nodeViewEventBusEmitSpy = vi.spyOn(nodeViewEventBus, 'emit');

describe('NodeExecuteButton', () => {
beforeEach(() => {
vi.clearAllMocks();
Expand Down Expand Up @@ -356,22 +353,27 @@ describe('NodeExecuteButton', () => {
await userEvent.click(getByRole('button'));

expect(ndvStore.unsetActiveNodeName).toHaveBeenCalled();
expect(workflowsStore.chatPartialExecutionDestinationNode).toBe(node.name);
expect(nodeViewEventBusEmitSpy).toHaveBeenCalledWith('openChat');
expect(runWorkflow.runWorkflow).toHaveBeenCalledWith({
destinationNode: { nodeName: node.name, mode: 'inclusive' },
source: 'RunData.ExecuteNodeButton',
});
});

it('opens chat when clicking button for chat child node', async () => {
const node = mockNode({ name: 'test-node', type: SET_NODE_TYPE });
vi.spyOn(workflowDocumentStore, 'getNodeByName').mockReturnValue(node);
workflowsStore.checkIfNodeHasChatParent.mockReturnValue(true);
workflowsStore.workflowObject.getStartNode = vi.fn().mockReturnValue(undefined);

const { getByRole } = renderComponent();

await userEvent.click(getByRole('button'));

expect(ndvStore.unsetActiveNodeName).toHaveBeenCalled();
expect(workflowsStore.chatPartialExecutionDestinationNode).toBe(node.name);
expect(nodeViewEventBusEmitSpy).toHaveBeenCalledWith('openChat');
expect(runWorkflow.runWorkflow).toHaveBeenCalledWith({
destinationNode: { nodeName: node.name, mode: 'inclusive' },
source: 'RunData.ExecuteNodeButton',
});
});

it('prompts for confirmation when pinned data exists', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
type WorkflowState,
} from '@/app/composables/useWorkflowState';
import { useUIStore } from '@/app/stores/ui.store';
import { nodeViewEventBus } from '@/app/event-bus';
import { needsAgentInput } from '@/app/utils/nodes/nodeTransforms';
import { generateCodeForAiTransform } from '@/features/ndv/parameters/utils/buttonParameter.utils';
import type { INodeUi } from '@/Interface';
Expand All @@ -37,6 +36,7 @@ const {
mockPinnedData,
mockMessage,
mockWorkflowDocumentStore,
mockNodeHelpers,
} = vi.hoisted(() => ({
mockWorkflowsStore: {
isWorkflowRunning: false,
Expand All @@ -47,6 +47,9 @@ const {
checkIfNodeHasChatParent: vi.fn(),
getNodeByName: vi.fn(),
removeTestWebhook: vi.fn(),
workflowObject: {
getStartNode: vi.fn(),
},
},
mockNodeTypesStore: {
getNodeType: vi.fn(),
Expand All @@ -71,6 +74,11 @@ const {
},
mockWorkflowDocumentStore: {
updateNodeProperties: vi.fn(),
getNodeByName: vi.fn(),
pinData: {} as Record<string, unknown>,
},
mockNodeHelpers: {
getNodeInputData: vi.fn().mockReturnValue([]),
},
}));

Expand Down Expand Up @@ -110,6 +118,10 @@ vi.mock('@/app/composables/useRunWorkflow', () => ({
useRunWorkflow: vi.fn().mockReturnValue(mockRunWorkflow),
}));

vi.mock('@/app/composables/useNodeHelpers', () => ({
useNodeHelpers: vi.fn().mockReturnValue(mockNodeHelpers),
}));

vi.mock('@/app/composables/usePinnedData', () => ({
usePinnedData: vi.fn().mockReturnValue(mockPinnedData),
}));
Expand Down Expand Up @@ -161,10 +173,6 @@ vi.mock('@/features/ndv/parameters/utils/buttonParameter.utils', () => ({
generateCodeForAiTransform: vi.fn(),
}));

vi.mock('@/app/event-bus', () => ({
nodeViewEventBus: { emit: vi.fn() },
}));

function createTestNode(overrides: Partial<INodeUi> = {}): INodeUi {
return {
id: 'test-id',
Expand Down Expand Up @@ -198,6 +206,11 @@ describe('useNodeExecution', () => {
mockWorkflowsStore.checkIfNodeHasChatParent.mockReturnValue(false);
mockWorkflowsStore.removeTestWebhook.mockReset();
mockWorkflowsStore.getNodeByName.mockReset();
mockWorkflowsStore.workflowObject.getStartNode.mockReset();

mockNodeHelpers.getNodeInputData.mockReset().mockReturnValue([]);
mockWorkflowDocumentStore.getNodeByName.mockReset();
mockWorkflowDocumentStore.pinData = {};

mockNodeTypesStore.getNodeType.mockReturnValue(null);
mockNodeTypesStore.isTriggerNode.mockReturnValue(false);
Expand Down Expand Up @@ -660,13 +673,18 @@ describe('useNodeExecution', () => {

expect(result).toBe('opened-chat');
expect(mockNdvStore.unsetActiveNodeName).toHaveBeenCalled();
expect(nodeViewEventBus.emit).toHaveBeenCalledWith('openChat');
expect(mockWorkflowsStore.chatPartialExecutionDestinationNode).toBe('Chat Node');
expect(mockRunWorkflow.runWorkflow).toHaveBeenCalledWith(
expect.objectContaining({ destinationNode: { nodeName: 'Chat Node', mode: 'inclusive' } }),
);
});

it('should open chat for chat child nodes when input panel is empty', async () => {
it('should open chat for chat child nodes when chat trigger has no data', async () => {
mockWorkflowsStore.checkIfNodeHasChatParent.mockReturnValue(true);
mockNdvStore.isInputPanelEmpty = true;
mockWorkflowsStore.workflowObject.getStartNode.mockReturnValue({
name: 'Chat Trigger',
type: CHAT_TRIGGER_NODE_TYPE,
});
mockNodeHelpers.getNodeInputData.mockReturnValue([]);
const node = ref(createTestNode({ name: 'Child Node' }));

const { execute } = useNodeExecution(node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { useNDVStore } from '@/features/ndv/shared/ndv.store';
import { useUIStore } from '@/app/stores/ui.store';

import { useRunWorkflow } from '@/app/composables/useRunWorkflow';
import { useNodeHelpers } from '@/app/composables/useNodeHelpers';
import { usePinnedData } from '@/app/composables/usePinnedData';
import { useMessage } from '@/app/composables/useMessage';
import { useTelemetry } from '@/app/composables/useTelemetry';
Expand All @@ -31,8 +32,6 @@ import {
import { needsAgentInput } from '@/app/utils/nodes/nodeTransforms';
import { generateCodeForAiTransform } from '@/features/ndv/parameters/utils/buttonParameter.utils';

import { nodeViewEventBus } from '@/app/event-bus';

import {
WEBHOOK_NODE_TYPE,
MANUAL_TRIGGER_NODE_TYPE,
Expand Down Expand Up @@ -111,6 +110,7 @@ export function useNodeExecution(
);

const { runWorkflow, stopCurrentExecution } = useRunWorkflow({ router });
const nodeHelpers = useNodeHelpers();

const codeGenerationInProgress = ref(false);

Expand Down Expand Up @@ -341,6 +341,15 @@ export function useNodeExecution(
return true;
}

function chatTriggerHasInputData(): boolean {
if (!nodeRef.value) return false;
const startNode = workflowsStore.workflowObject.getStartNode(nodeRef.value.name);
if (!startNode || startNode.type !== CHAT_TRIGGER_NODE_TYPE) return false;
const hasRunData = nodeHelpers.getNodeInputData(startNode, 0, 0, 'input')?.length > 0;
const hasPinData = !!workflowDocumentStore.value?.pinData?.[startNode.name];
return hasRunData || hasPinData;
}

async function execute(): Promise<ExecuteAction> {
if (!nodeRef.value) return 'noop';

Expand All @@ -352,11 +361,14 @@ export function useNodeExecution(
if (!success) return 'cancelled';
}

// Chat nodes
if (isChatNode.value || (isChatChild.value && ndvStore.isInputPanelEmpty)) {
// Chat nodes — open chat when: it's a chat trigger itself, or it's a child of
// a chat trigger that has no execution/pin data yet (needs chat input first).
if (isChatNode.value || (isChatChild.value && !chatTriggerHasInputData())) {
ndvStore.unsetActiveNodeName();
workflowsStore.chatPartialExecutionDestinationNode = nodeName;
nodeViewEventBus.emit('openChat');
await runWorkflow({
destinationNode: { nodeName, mode: toValue(executionMode) },
source,
});
return 'opened-chat';
}

Expand Down
Loading