fix(editor): Use workflow-scoped credential fetch in node credential picker (#31938)

This commit is contained in:
Yuliia Pominchuk
2026-06-09 10:04:15 +00:00
committed by GitHub
parent 073187137a
commit 8cd67d300d
2 changed files with 55 additions and 5 deletions
@@ -21,6 +21,7 @@ import { useNDVStore } from '@/features/ndv/shared/ndv.store';
import { useUIStore } from '@/app/stores/ui.store';
import { useSettingsStore } from '@/app/stores/settings.store';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import { useWorkflowsStore } from '@/app/stores/workflows.store';
import { useAiGateway } from '@/app/composables/useAiGateway';
import { WorkflowDocumentStoreKey } from '@/app/constants/injectionKeys';
import {
@@ -153,6 +154,7 @@ describe('NodeCredentials', () => {
let uiStore: ReturnType<typeof mockedStore<typeof useUIStore>>;
let projectsStore: ReturnType<typeof mockedStore<typeof useProjectsStore>>;
let settingsStore: ReturnType<typeof mockedStore<typeof useSettingsStore>>;
let workflowsStore: ReturnType<typeof mockedStore<typeof useWorkflowsStore>>;
let workflowDocumentStore: ReturnType<typeof useWorkflowDocumentStore>;
let workflowDocumentStoreRef: ReturnType<
typeof shallowRef<ReturnType<typeof useWorkflowDocumentStore> | null>
@@ -190,11 +192,15 @@ describe('NodeCredentials', () => {
credentialsStore = mockedStore(useCredentialsStore);
// Component triggers this on mount; avoid a real XHR with stubActions: false.
credentialsStore.fetchAllCredentials = vi.fn().mockResolvedValue([]);
credentialsStore.fetchAllCredentialsForWorkflow = vi.fn().mockResolvedValue([]);
ndvStore = mockedStore(useNDVStore, createWorkflowDocumentId('1'));
uiStore = mockedStore(useUIStore);
projectsStore = mockedStore(useProjectsStore);
settingsStore = mockedStore(useSettingsStore);
workflowsStore = mockedStore(useWorkflowsStore);
workflowsStore.isNewWorkflow = false;
projectsStore.currentProject = { id: 'default', scopes: ['credential:create'] } as Project;
settingsStore.settings = {
@@ -231,7 +237,36 @@ describe('NodeCredentials', () => {
renderComponent();
expect(credentialsStore.fetchAllCredentials).toHaveBeenCalled();
expect(credentialsStore.fetchAllCredentialsForWorkflow).toHaveBeenCalledWith({
workflowId: '1',
});
});
it('should fetch credentials scoped to the project for an unsaved workflow', () => {
workflowsStore.isNewWorkflow = true;
projectsStore.currentProject = { id: 'project-1' } as Project;
ndvStore.activeNode = httpNode;
credentialsStore.state.credentials = {};
renderComponent();
expect(credentialsStore.fetchAllCredentialsForWorkflow).toHaveBeenCalledWith({
projectId: 'project-1',
});
});
it('should fall back to the personal project for an unsaved workflow without a current project', () => {
workflowsStore.isNewWorkflow = true;
projectsStore.currentProject = null;
projectsStore.personalProject = { id: 'personal-project' } as Project;
ndvStore.activeNode = httpNode;
credentialsStore.state.credentials = {};
renderComponent();
expect(credentialsStore.fetchAllCredentialsForWorkflow).toHaveBeenCalledWith({
projectId: 'personal-project',
});
});
it('should ignore managed credentials in the dropdown if active node is the HTTP node', async () => {
@@ -32,6 +32,7 @@ import { injectNDVStore } from '@/features/ndv/shared/ndv.store';
import { useNodeTypesStore } from '@/app/stores/nodeTypes.store';
import { useUIStore } from '@/app/stores/ui.store';
import { useProjectsStore } from '@/features/collaboration/projects/projects.store';
import { useWorkflowsStore } from '@/app/stores/workflows.store';
import { assert } from '@n8n/utils/assert';
import { isEmpty } from '@/app/utils/typesUtils';
import { getResourcePermissions } from '@n8n/permissions';
@@ -97,6 +98,7 @@ const nodeTypesStore = useNodeTypesStore();
const ndvStore = injectNDVStore();
const uiStore = useUIStore();
const projectsStore = useProjectsStore();
const workflowsStore = useWorkflowsStore();
const workflowDocumentStore = props.standalone ? undefined : injectWorkflowDocumentStore();
const { isEnabled: isDynamicCredentialsEnabled } = useDynamicCredentials();
@@ -256,6 +258,18 @@ watch(
{ immediate: true },
);
function getCredentialFetchScope(): { workflowId: string } | { projectId: string } | undefined {
const workflowId = workflowDocumentStore?.value.workflowId;
if (workflowId && !workflowsStore.isNewWorkflow) {
return { workflowId };
}
const projectId =
props.projectId ?? projectsStore.currentProject?.id ?? projectsStore.personalProject?.id;
return projectId ? { projectId } : undefined;
}
onMounted(() => {
credentialsStore.$onAction(({ name, after, args }) => {
const listeningForActions = ['createNewCredential', 'updateCredential', 'deleteCredential'];
@@ -325,9 +339,10 @@ onMounted(() => {
ndvEventBus.on('credential.createNew', onCreateAndAssignNewCredential);
void credentialsStore.fetchAllCredentials({
projectId: projectsStore.currentProject?.id,
});
const scope = getCredentialFetchScope();
if (scope) {
void credentialsStore.fetchAllCredentialsForWorkflow(scope);
}
void aiGateway.fetchConfig();