fix(editor): Prevent NodeCreator from swallowing AskAssistant enter event (#11532)

This commit is contained in:
Charlie Kolb 2024-11-07 16:22:13 +01:00 committed by GitHub
parent 6e2809b490
commit db94f169fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 53 additions and 16 deletions

View file

@ -4,6 +4,7 @@ import { clickCreateNewCredential, openCredentialSelect } from '../composables/n
import { GMAIL_NODE_NAME, SCHEDULE_TRIGGER_NODE_NAME } from '../constants';
import { CredentialsModal, CredentialsPage, NDV, WorkflowPage } from '../pages';
import { AIAssistant } from '../pages/features/ai-assistant';
import { NodeCreator } from '../pages/features/node-creator';
import { getVisibleSelect } from '../utils';
const wf = new WorkflowPage();
@ -11,6 +12,7 @@ const ndv = new NDV();
const aiAssistant = new AIAssistant();
const credentialsPage = new CredentialsPage();
const credentialsModal = new CredentialsModal();
const nodeCreatorFeature = new NodeCreator();
describe('AI Assistant::disabled', () => {
beforeEach(() => {
@ -280,6 +282,20 @@ describe('AI Assistant::enabled', () => {
wf.getters.isWorkflowSaved();
aiAssistant.getters.placeholderMessage().should('not.exist');
});
it('should send message via enter even with global NodeCreator panel opened', () => {
cy.intercept('POST', '/rest/ai/chat', {
statusCode: 200,
fixture: 'aiAssistant/responses/simple_message_response.json',
}).as('chatRequest');
wf.actions.addInitialNodeToCanvas(SCHEDULE_TRIGGER_NODE_NAME);
aiAssistant.actions.openChat();
nodeCreatorFeature.actions.openNodeCreator();
aiAssistant.getters.chatInput().type('Hello{Enter}');
aiAssistant.getters.placeholderMessage().should('not.exist');
});
});
describe('AI Assistant Credential Help', () => {

View file

@ -317,6 +317,7 @@ async function onCopyButtonClick(content: string, e: MouseEvent) {
<textarea
ref="chatInput"
v-model="textInputValue"
class="ignore-key-press-node-creator ignore-key-press-canvas"
:disabled="sessionEnded"
:placeholder="t('assistantChat.inputPlaceholder')"
rows="1"

View file

@ -156,6 +156,7 @@ exports[`AskAssistantChat > does not render retry button if no error is present
data-test-id="chat-input-wrapper"
>
<textarea
class="ignore-key-press-node-creator ignore-key-press-canvas"
data-test-id="chat-input"
placeholder="Enter your response..."
rows="1"
@ -903,6 +904,7 @@ Testing more code
data-test-id="chat-input-wrapper"
>
<textarea
class="ignore-key-press-node-creator ignore-key-press-canvas"
data-test-id="chat-input"
placeholder="Enter your response..."
rows="1"
@ -1078,6 +1080,7 @@ exports[`AskAssistantChat > renders default placeholder chat correctly 1`] = `
data-test-id="chat-input-wrapper"
>
<textarea
class="ignore-key-press-node-creator ignore-key-press-canvas"
data-test-id="chat-input"
placeholder="Enter your response..."
rows="1"
@ -1323,6 +1326,7 @@ exports[`AskAssistantChat > renders end of session chat correctly 1`] = `
data-test-id="chat-input-wrapper"
>
<textarea
class="ignore-key-press-node-creator ignore-key-press-canvas"
data-test-id="chat-input"
disabled=""
placeholder="Enter your response..."
@ -1493,6 +1497,7 @@ exports[`AskAssistantChat > renders error message correctly with retry button 1`
data-test-id="chat-input-wrapper"
>
<textarea
class="ignore-key-press-node-creator ignore-key-press-canvas"
data-test-id="chat-input"
placeholder="Enter your response..."
rows="1"
@ -1737,6 +1742,7 @@ catch(e) {
data-test-id="chat-input-wrapper"
>
<textarea
class="ignore-key-press-node-creator ignore-key-press-canvas"
data-test-id="chat-input"
placeholder="Enter your response..."
rows="1"
@ -1913,6 +1919,7 @@ exports[`AskAssistantChat > renders streaming chat correctly 1`] = `
data-test-id="chat-input-wrapper"
>
<textarea
class="ignore-key-press-node-creator ignore-key-press-canvas"
data-test-id="chat-input"
placeholder="Enter your response..."
rows="1"

View file

@ -62,6 +62,16 @@ export const useKeyboardNavigation = defineStore('nodeCreatorKeyboardNavigation'
}
async function onKeyDown(e: KeyboardEvent) {
// We generally want a global listener across the app
// But specific components may overrule this by adopting
// the 'ignore-key-press-node-creator' class
if (
e.target instanceof Element &&
e.target.classList.contains('ignore-key-press-node-creator')
) {
return;
}
const pressedKey = e.key;
if (!WATCHED_KEYS.includes(pressedKey)) return;
e.preventDefault();

View file

@ -1023,7 +1023,7 @@ onUpdated(async () => {
@update:model-value="expressionUpdated"
></ExpressionEditModal>
<div class="parameter-input ignore-key-press" :style="parameterInputWrapperStyle">
<div class="parameter-input ignore-key-press-canvas" :style="parameterInputWrapperStyle">
<ResourceLocator
v-if="parameter.type === 'resourceLocator'"
ref="resourceLocator"
@ -1098,7 +1098,10 @@ onUpdated(async () => {
:before-close="closeCodeEditDialog"
data-test-id="code-editor-fullscreen"
>
<div :key="codeEditDialogVisible.toString()" class="ignore-key-press code-edit-dialog">
<div
:key="codeEditDialogVisible.toString()"
class="ignore-key-press-canvas code-edit-dialog"
>
<CodeNodeEditor
v-if="editorType === 'codeNodeEditor'"
:mode="codeEditorMode"

View file

@ -1409,7 +1409,7 @@ defineExpose({ enterEditMode });
</div>
<div v-else-if="editMode.enabled" :class="$style.editMode">
<div :class="[$style.editModeBody, 'ignore-key-press']">
<div :class="[$style.editModeBody, 'ignore-key-press-canvas']">
<JsonEditor
:model-value="editMode.value"
:fill-parent="true"

View file

@ -68,7 +68,7 @@ const closeDialog = () => {
.inputLabelDisplayName(parameter, path)}`"
:before-close="closeDialog"
>
<div class="ignore-key-press">
<div class="ignore-key-press-canvas">
<n8n-input-label :label="$locale.nodeText().inputLabelDisplayName(parameter, path)">
<div @keydown.stop @keydown.esc="onKeyDownEsc">
<n8n-input

View file

@ -536,7 +536,7 @@ onMounted(() => {
data-test-id="workflow-lm-chat-dialog"
:style="messageVars"
>
<MessagesList :messages="messages" :class="[$style.messages, 'ignore-key-press']">
<MessagesList :messages="messages" :class="[$style.messages, 'ignore-key-press-canvas']">
<template #beforeMessage="{ message }">
<MessageOptionTooltip
v-if="message.sender === 'bot' && !message.id.includes('preload')"

View file

@ -486,7 +486,7 @@ onMounted(async () => {
<el-col :span="10" class="setting-name">
{{ $locale.baseText('workflowSettings.executionOrder') + ':' }}
</el-col>
<el-col :span="14" class="ignore-key-press">
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
v-model="workflowSettings.executionOrder"
placeholder="Select Execution Order"
@ -517,7 +517,7 @@ onMounted(async () => {
<font-awesome-icon icon="question-circle" />
</n8n-tooltip>
</el-col>
<el-col :span="14" class="ignore-key-press">
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
v-model="workflowSettings.errorWorkflow"
placeholder="Select Workflow"
@ -548,7 +548,7 @@ onMounted(async () => {
</n8n-tooltip>
</el-col>
<el-col :span="14" class="ignore-key-press">
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
v-model="workflowSettings.callerPolicy"
:disabled="readOnlyEnv || !workflowPermissions.update"
@ -598,7 +598,7 @@ onMounted(async () => {
<font-awesome-icon icon="question-circle" />
</n8n-tooltip>
</el-col>
<el-col :span="14" class="ignore-key-press">
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
v-model="workflowSettings.timezone"
placeholder="Select Timezone"
@ -627,7 +627,7 @@ onMounted(async () => {
<font-awesome-icon icon="question-circle" />
</n8n-tooltip>
</el-col>
<el-col :span="14" class="ignore-key-press">
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
v-model="workflowSettings.saveDataErrorExecution"
:placeholder="$locale.baseText('workflowSettings.selectOption')"
@ -656,7 +656,7 @@ onMounted(async () => {
<font-awesome-icon icon="question-circle" />
</n8n-tooltip>
</el-col>
<el-col :span="14" class="ignore-key-press">
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
v-model="workflowSettings.saveDataSuccessExecution"
:placeholder="$locale.baseText('workflowSettings.selectOption')"
@ -685,7 +685,7 @@ onMounted(async () => {
<font-awesome-icon icon="question-circle" />
</n8n-tooltip>
</el-col>
<el-col :span="14" class="ignore-key-press">
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
v-model="workflowSettings.saveManualExecutions"
:placeholder="$locale.baseText('workflowSettings.selectOption')"
@ -714,7 +714,7 @@ onMounted(async () => {
<font-awesome-icon icon="question-circle" />
</n8n-tooltip>
</el-col>
<el-col :span="14" class="ignore-key-press">
<el-col :span="14" class="ignore-key-press-canvas">
<n8n-select
v-model="workflowSettings.saveExecutionProgress"
:placeholder="$locale.baseText('workflowSettings.selectOption')"

View file

@ -14,7 +14,7 @@ export function useClipboard(
const { debounce } = useDebounce();
const { copy, copied, isSupported, text } = useClipboardCore({ legacy: true });
const ignoreClasses = ['el-messsage-box', 'ignore-key-press'];
const ignoreClasses = ['el-messsage-box', 'ignore-key-press-canvas'];
const initialized = ref(false);
const onPasteCallback = ref<ClipboardEventFn | null>(options.onPaste || null);

View file

@ -22,7 +22,7 @@ export const useKeybindings = (
const active = activeElement.value;
const isInput = ['INPUT', 'TEXTAREA'].includes(active.tagName);
const isContentEditable = active.closest('[contenteditable]') !== null;
const isIgnoreClass = active.closest('.ignore-key-press') !== null;
const isIgnoreClass = active.closest('.ignore-key-press-canvas') !== null;
return isInput || isContentEditable || isIgnoreClass;
});

View file

@ -1268,7 +1268,7 @@ export default defineComponent({
element instanceof HTMLElement &&
element.className &&
typeof element.className === 'string' &&
element.className.includes('ignore-key-press')
element.className.includes('ignore-key-press-canvas')
) {
return;
}