mirror of
https://github.com/n8n-io/n8n.git
synced 2026-10-19 16:55:02 +02:00
fix(editor): Show agent tool credentials above configuration (#35590)
This commit is contained in:
parent
bbf5da6350
commit
1ca0708ba8
|
|
@ -5,6 +5,7 @@ import { mockedStore } from '@/__tests__/utils';
|
|||
import { useUIStore } from '@/app/stores/ui.store';
|
||||
import { fireEvent, waitFor } from '@testing-library/vue';
|
||||
import { defineComponent, onMounted, nextTick } from 'vue';
|
||||
import { CREDENTIAL_EDIT_MODAL_KEY } from '@/features/credentials/credentials.constants';
|
||||
|
||||
import AgentToolConfigModal from '../components/AgentToolConfigModal.vue';
|
||||
import type { AgentJsonToolRef, CustomToolEntry } from '../types';
|
||||
|
|
@ -31,9 +32,24 @@ vi.mock('@n8n/design-system', async () => {
|
|||
const actual = await vi.importActual<typeof import('@n8n/design-system')>('@n8n/design-system');
|
||||
const N8nDialog = {
|
||||
name: 'N8nDialog',
|
||||
props: ['open', 'size', 'showCloseButton'],
|
||||
emits: ['update:open'],
|
||||
template: '<div v-if="open" role="dialog"><slot /></div>',
|
||||
props: {
|
||||
open: Boolean,
|
||||
size: String,
|
||||
showCloseButton: Boolean,
|
||||
trapFocus: { type: Boolean, default: true },
|
||||
disableOutsidePointerEvents: { type: Boolean, default: true },
|
||||
},
|
||||
emits: ['update:open', 'interactOutside'],
|
||||
template: `
|
||||
<div
|
||||
v-if="open"
|
||||
role="dialog"
|
||||
:data-trap-focus="trapFocus"
|
||||
:data-disable-outside-pointer-events="disableOutsidePointerEvents"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
`,
|
||||
};
|
||||
return { ...actual, N8nDialog };
|
||||
});
|
||||
|
|
@ -141,6 +157,9 @@ function renderModal({
|
|||
global: {
|
||||
stubs: {
|
||||
NodeIcon: { template: '<div data-test-id="header-node-icon" />' },
|
||||
FocusScope: {
|
||||
template: '<div data-test-id="nested-credential-focus-scope"><slot /></div>',
|
||||
},
|
||||
AgentToolConfigNodeContent: createToolSettingsStub(valid),
|
||||
AgentToolConfigWorkflowContent: createWorkflowToolConfigStub(valid),
|
||||
N8nSwitch2: {
|
||||
|
|
@ -180,6 +199,22 @@ describe('AgentToolConfigModal', () => {
|
|||
expect(getByTestId('node-tool-settings-content')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('releases dialog focus handling while the credential modal is open', async () => {
|
||||
const { getByRole, getByTestId, queryByTestId } = renderModal();
|
||||
const dialog = getByRole('dialog');
|
||||
|
||||
expect(dialog).toHaveAttribute('data-trap-focus', 'true');
|
||||
expect(dialog).toHaveAttribute('data-disable-outside-pointer-events', 'true');
|
||||
expect(queryByTestId('nested-credential-focus-scope')).toBeNull();
|
||||
|
||||
uiStore.openModal(CREDENTIAL_EDIT_MODAL_KEY);
|
||||
await nextTick();
|
||||
|
||||
expect(dialog).toHaveAttribute('data-trap-focus', 'false');
|
||||
expect(dialog).toHaveAttribute('data-disable-outside-pointer-events', 'false');
|
||||
expect(getByTestId('nested-credential-focus-scope')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('passes agent project context to the node-tool settings content', () => {
|
||||
const { getByTestId } = renderModal({ projectId: 'project-1', agentId: 'agent-1' });
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ import {
|
|||
} from '@n8n/design-system';
|
||||
import { useI18n } from '@n8n/i18n';
|
||||
import type { INode } from 'n8n-workflow';
|
||||
import { FocusScope } from 'reka-ui';
|
||||
|
||||
import { CREDENTIAL_EDIT_MODAL_KEY } from '@/features/credentials/credentials.constants';
|
||||
import type {
|
||||
AgentJsonMcpServerConfig,
|
||||
AgentJsonToolRef,
|
||||
|
|
@ -73,6 +75,9 @@ const props = defineProps<{
|
|||
|
||||
const i18n = useI18n();
|
||||
const uiStore = useUIStore();
|
||||
const isCredentialModalOpen = computed(
|
||||
() => uiStore.modalsById[CREDENTIAL_EDIT_MODAL_KEY]?.open === true,
|
||||
);
|
||||
|
||||
const isOpen = computed({
|
||||
get: () => uiStore.modalsById[props.modalName]?.open === true,
|
||||
|
|
@ -203,6 +208,10 @@ function closeDialog() {
|
|||
uiStore.closeModal(props.modalName);
|
||||
}
|
||||
|
||||
function handleInteractOutside(event: Event) {
|
||||
if (isCredentialModalOpen.value) event.preventDefault();
|
||||
}
|
||||
|
||||
function withApprovalRequirement(ref: AgentJsonToolRef): AgentJsonToolRef {
|
||||
if (!supportsApproval.value) {
|
||||
const { requireApproval: _requireApproval, ...rest } = ref;
|
||||
|
|
@ -311,9 +320,21 @@ function handleNodeUpdate(node: INode) {
|
|||
v-if="canRender"
|
||||
v-model:open="isOpen"
|
||||
size="2xlarge"
|
||||
:trap-focus="!isCredentialModalOpen"
|
||||
:disable-outside-pointer-events="!isCredentialModalOpen"
|
||||
:show-close-button="false"
|
||||
data-test-id="agent-tool-config-modal"
|
||||
@interact-outside="handleInteractOutside"
|
||||
>
|
||||
<FocusScope
|
||||
v-if="isCredentialModalOpen"
|
||||
as-child
|
||||
@mount-auto-focus.prevent
|
||||
@unmount-auto-focus.prevent
|
||||
>
|
||||
<span hidden aria-hidden="true" />
|
||||
</FocusScope>
|
||||
|
||||
<N8nDialogHeader>
|
||||
<AgentToolConfigModalHeader
|
||||
:kind="headerKind"
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@ describe('NodeCredentials', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should hide the assistant when opening credentials from a tool context', async () => {
|
||||
it('should configure the new credential modal for a tool context', async () => {
|
||||
ndvStore.activeNode = httpNode;
|
||||
credentialsStore.state.credentials = {
|
||||
c8vqdPpPClh4TgIO: createCredential(),
|
||||
|
|
@ -394,7 +394,7 @@ describe('NodeCredentials', () => {
|
|||
undefined,
|
||||
httpNode.name,
|
||||
httpNode,
|
||||
{ hideAskAssistant: true, closeOnSave: true },
|
||||
{ hideAskAssistant: true, closeOnSave: true, appendToBody: true },
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -1282,6 +1282,32 @@ describe('NodeCredentials', () => {
|
|||
|
||||
expect(screen.queryByTestId('credential-edit-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should configure the edit credential modal for a tool context', async () => {
|
||||
ndvStore.activeNode = httpNode;
|
||||
credentialsStore.state.credentials = {
|
||||
c8vqdPpPClh4TgIO: createCredential(),
|
||||
};
|
||||
|
||||
renderComponent({
|
||||
global: {
|
||||
provide: {
|
||||
[ChatHubToolContextKey as symbol]: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const editIcon = screen
|
||||
.getByTestId('credential-edit-button')
|
||||
.querySelector('[data-icon="pen"]');
|
||||
expect(editIcon).not.toBeNull();
|
||||
await userEvent.click(editIcon!);
|
||||
|
||||
expect(uiStore.openExistingCredential).toHaveBeenCalledWith('c8vqdPpPClh4TgIO', {
|
||||
hideAskAssistant: true,
|
||||
appendToBody: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not show "Set up credential" button when user cannot create credentials', () => {
|
||||
|
|
|
|||
|
|
@ -525,6 +525,7 @@ function createNewCredential(
|
|||
{
|
||||
hideAskAssistant: hideAskAssistant.value,
|
||||
closeOnSave: true,
|
||||
...(isToolContext ? { appendToBody: true } : {}),
|
||||
instanceAiCredentialHelp: instanceAiCredentialHelp(),
|
||||
},
|
||||
);
|
||||
|
|
@ -815,6 +816,7 @@ function editCredential(credentialType: string): void {
|
|||
|
||||
uiStore.openExistingCredential(credential.id, {
|
||||
hideAskAssistant: hideAskAssistant.value,
|
||||
...(isToolContext ? { appendToBody: true } : {}),
|
||||
instanceAiCredentialHelp: instanceAiCredentialHelp(),
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user