fix(editor): Hide Execute and refine section when error occurs after workflow update (#20876)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Mutasem Aldmour
2025-10-16 17:43:47 +02:00
committed by GitHub
co-authored by Claude
parent fa0278647e
commit 3d9d19ef81
2 changed files with 193 additions and 3 deletions
@@ -7,6 +7,7 @@ interface VueComponentInstance {
setupState?: {
onUserMessage?: (message: string) => Promise<void>;
showAskOwnerTooltip?: boolean;
showExecuteMessage?: boolean;
};
};
}
@@ -22,6 +23,17 @@ vi.mock('@/composables/useWorkflowSaving', () => ({
}),
}));
// Mock ExecuteMessage component
vi.mock('./ExecuteMessage.vue', () => ({
default: defineComponent({
name: 'ExecuteMessage',
emits: ['workflow-executed'],
setup() {
return () => h('div', { 'data-test-id': 'execute-message-component' }, 'Execute and refine');
},
}),
}));
// Mock AskAssistantChat component
vi.mock('@n8n/design-system/components/AskAssistantChat/AskAssistantChat.vue', () => ({
default: defineComponent({
@@ -36,7 +48,7 @@ vi.mock('@n8n/design-system/components/AskAssistantChat/AskAssistantChat.vue', (
'showAskOwnerTooltip',
],
emits: ['message', 'feedback', 'stop', 'upgrade-click'],
setup(props, { emit, expose }) {
setup(props, { emit, expose, slots }) {
const feedbackText = { value: '' };
const sendMessage = (message: string) => {
@@ -44,7 +56,7 @@ vi.mock('@n8n/design-system/components/AskAssistantChat/AskAssistantChat.vue', (
};
expose({ sendMessage });
// Create a more realistic mock that includes rating buttons when needed
// Create a more realistic mock that includes rating buttons and slots when needed
return () => {
const lastMessage = props.messages?.[props.messages.length - 1];
const showRating = lastMessage?.showRating;
@@ -74,6 +86,8 @@ vi.mock('@n8n/design-system/components/AskAssistantChat/AskAssistantChat.vue', (
}),
]
: null,
// Render messagesFooter slot if it exists
slots.messagesFooter?.(),
]);
};
},
@@ -1118,6 +1132,175 @@ describe('AskAssistantBuild', () => {
});
});
describe('Execute and refine section visibility', () => {
it('should hide ExecuteMessage component when there is an error after workflow update', async () => {
// Setup: workflow with nodes
workflowsStore.$patch({
workflow: {
nodes: [
{
id: 'node1',
name: 'Start',
type: 'n8n-nodes-base.start',
position: [0, 0],
typeVersion: 1,
parameters: {},
} as INodeUi,
],
connections: {},
},
});
const { queryByTestId } = renderComponent();
// Simulate workflow update message followed by error
builderStore.$patch({
streaming: false,
chatMessages: [
{ id: '1', role: 'user', type: 'text', content: 'Create a workflow' },
{
id: '2',
role: 'assistant',
type: 'workflow-updated',
codeSnippet: JSON.stringify({ nodes: [], connections: {} }),
},
{ id: '3', role: 'assistant', type: 'error', content: 'An error occurred' },
],
});
await flushPromises();
// Verify the ExecuteMessage component should NOT be rendered
expect(queryByTestId('execute-message-component')).not.toBeInTheDocument();
});
it('should show ExecuteMessage component when there is NO error after workflow update', async () => {
// Setup: workflow with nodes
workflowsStore.$patch({
workflow: {
nodes: [
{
id: 'node1',
name: 'Start',
type: 'n8n-nodes-base.start',
position: [0, 0],
typeVersion: 1,
parameters: {},
} as INodeUi,
],
connections: {},
},
});
const { queryByTestId } = renderComponent();
// Simulate workflow update message WITHOUT error
builderStore.$patch({
streaming: false,
chatMessages: [
{ id: '1', role: 'user', type: 'text', content: 'Create a workflow' },
{
id: '2',
role: 'assistant',
type: 'workflow-updated',
codeSnippet: JSON.stringify({ nodes: [], connections: {} }),
},
{ id: '3', role: 'assistant', type: 'text', content: 'Workflow created successfully' },
],
});
await flushPromises();
// Verify the ExecuteMessage component SHOULD be rendered
expect(queryByTestId('execute-message-component')).toBeInTheDocument();
});
it('should show ExecuteMessage component when error occurs BEFORE workflow update', async () => {
// Setup: workflow with nodes
workflowsStore.$patch({
workflow: {
nodes: [
{
id: 'node1',
name: 'Start',
type: 'n8n-nodes-base.start',
position: [0, 0],
typeVersion: 1,
parameters: {},
} as INodeUi,
],
connections: {},
},
});
const { queryByTestId } = renderComponent();
// Simulate error BEFORE workflow update
builderStore.$patch({
streaming: false,
chatMessages: [
{ id: '1', role: 'user', type: 'text', content: 'Create a workflow' },
{ id: '2', role: 'assistant', type: 'error', content: 'Initial error' },
{
id: '3',
role: 'assistant',
type: 'workflow-updated',
codeSnippet: JSON.stringify({ nodes: [], connections: {} }),
},
{ id: '4', role: 'assistant', type: 'text', content: 'Recovered and created workflow' },
],
});
await flushPromises();
// Verify the ExecuteMessage component SHOULD be rendered because error was before workflow update
expect(queryByTestId('execute-message-component')).toBeInTheDocument();
});
it('should hide ExecuteMessage component when using update_node_parameters tool followed by error', async () => {
// Setup: workflow with nodes
workflowsStore.$patch({
workflow: {
nodes: [
{
id: 'node1',
name: 'HTTP Request',
type: 'n8n-nodes-base.httpRequest',
position: [0, 0],
typeVersion: 1,
parameters: {},
} as INodeUi,
],
connections: {},
},
});
const { queryByTestId } = renderComponent();
// Simulate update_node_parameters tool call followed by error
builderStore.$patch({
streaming: false,
chatMessages: [
{ id: '1', role: 'user', type: 'text', content: 'Update the HTTP node parameters' },
{
id: '2',
role: 'assistant',
type: 'tool',
toolName: 'update_node_parameters',
status: 'completed',
updates: [],
},
{ id: '3', role: 'assistant', type: 'error', content: 'Failed to update parameters' },
],
});
await flushPromises();
// Verify the ExecuteMessage component should NOT be rendered
expect(queryByTestId('execute-message-component')).not.toBeInTheDocument();
});
});
it('should handle multiple canvas generations correctly', async () => {
const originalWorkflow = {
nodes: [],
@@ -60,10 +60,17 @@ const showExecuteMessage = computed(() => {
msg.type === 'workflow-updated' ||
(msg.type === 'tool' && msg.toolName === 'update_node_parameters'),
);
// Check if there's an error message after the last workflow update
const hasErrorAfterUpdate = builderStore.chatMessages
.slice(builderUpdatedWorkflowMessageIndex + 1)
.some((msg) => msg.type === 'error');
return (
!builderStore.streaming &&
workflowsStore.workflow.nodes.length > 0 &&
builderUpdatedWorkflowMessageIndex > -1
builderUpdatedWorkflowMessageIndex > -1 &&
!hasErrorAfterUpdate
);
});
const creditsQuota = computed(() => builderStore.creditsQuota);