Skip to content
Closed
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 @@ -7,6 +7,7 @@ export interface WorkflowMetadata {
templateId?: string;
instanceId?: string;
templateCredsSetupCompleted?: boolean;
skipCredentialAutoOpen?: boolean;
}

// Simple version of n8n-workflow.Workflow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5024,6 +5024,25 @@ describe('useCanvasOperations', () => {
},
});
});

it('should set skipCredentialAutoOpen in workflow metadata when option is provided', async () => {
const template: WorkflowDataWithTemplateId = {
id: 'workflow-id',
name: 'Template Name',
nodes: [],
connections: {},
meta: { templateId: 'template-id' },
};

const workflowsStore = mockedStore(useWorkflowsStore);

const { openWorkflowTemplateFromJSON } = useCanvasOperations();
await openWorkflowTemplateFromJSON(template, { skipCredentialAutoOpen: true });

expect(workflowsStore.addToWorkflowMetadata).toHaveBeenCalledWith({
skipCredentialAutoOpen: true,
});
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2850,7 +2850,10 @@ export function useCanvasOperations() {
fitView();
}

async function openWorkflowTemplateFromJSON(workflow: WorkflowDataWithTemplateId) {
async function openWorkflowTemplateFromJSON(
workflow: WorkflowDataWithTemplateId,
options?: { skipCredentialAutoOpen?: boolean },
) {
if (!workflow.nodes || !workflow.connections) {
toast.showError(
new Error(i18n.baseText('nodeView.couldntLoadWorkflow.invalidWorkflowObject')),
Expand Down Expand Up @@ -2879,6 +2882,12 @@ export function useCanvasOperations() {
query: { templateId, parentFolderId, projectId: projectsStore.currentProjectId },
});

// Set skipCredentialAutoOpen BEFORE importTemplate to ensure it's available
// when SetupWorkflowCredentialsButton mounts (which happens during importTemplate)
if (options?.skipCredentialAutoOpen) {
workflowsStore.addToWorkflowMetadata({ skipCredentialAutoOpen: true });
}

await importTemplate({
id: templateId,
name: workflow.name,
Expand Down
3 changes: 2 additions & 1 deletion packages/frontend/editor-ui/src/app/views/NodeView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,8 @@ async function initializeRoute(force = false) {
return;
}

await openWorkflowTemplateFromJSON(workflow);
const skipCredentialAutoOpen = route.query.skipCredentialAutoOpen === 'true';
await openWorkflowTemplateFromJSON(workflow, { skipCredentialAutoOpen });
} else {
await openWorkflowTemplate(templateId.toString());
}
Expand Down
6 changes: 5 additions & 1 deletion packages/frontend/editor-ui/src/app/views/WorkflowsView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -1036,7 +1036,11 @@ const openAIWorkflow = async (source: string) => {
await router.push({
name: VIEWS.TEMPLATE_IMPORT,
params: { id: easyAiWorkflowJson.meta.templateId },
query: { fromJson: 'true', parentFolderId: route.params.folderId },
query: {
fromJson: 'true',
skipCredentialAutoOpen: 'true',
parentFolderId: route.params.folderId,
},
});
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import SetupWorkflowCredentialsButton from './SetupWorkflowCredentialsButton.vue
import { createTestingPinia } from '@pinia/testing';
import { mockedStore } from '@/__tests__/utils';
import { useWorkflowsStore } from '@/app/stores/workflows.store';
import { useUIStore } from '@/app/stores/ui.store';
import { usePostHog } from '@/app/stores/posthog.store';
import { TEMPLATE_SETUP_EXPERIENCE, SETUP_CREDENTIALS_MODAL_KEY } from '@/app/constants';
import { doesNodeHaveAllCredentialsFilled } from '@/app/utils/nodes/nodeTransforms';

vi.mock('vue-router', async () => {
const actual = await vi.importActual('vue-router');
Expand All @@ -20,6 +24,10 @@ vi.mock('vue-router', async () => {
};
});

vi.mock('@/app/utils/nodes/nodeTransforms', () => ({
doesNodeHaveAllCredentialsFilled: vi.fn(),
}));

let workflowsStore: ReturnType<typeof mockedStore<typeof useWorkflowsStore>>;

const renderComponent = createComponentRenderer(SetupWorkflowCredentialsButton);
Expand Down Expand Up @@ -55,4 +63,39 @@ describe('SetupWorkflowCredentialsButton', () => {
const { queryByTestId } = renderComponent();
expect(queryByTestId('setup-credentials-button')).toBeNull();
});

it('does not auto-open modal when skipCredentialAutoOpen is set in workflow meta', () => {
const uiStore = mockedStore(useUIStore);
const posthogStore = mockedStore(usePostHog);

// Enable the A/B test so modal would normally auto-open
posthogStore.getVariant.mockReturnValue(TEMPLATE_SETUP_EXPERIENCE.variant);

// Mock that nodes have unfilled credentials (so showButton would be true)
vi.mocked(doesNodeHaveAllCredentialsFilled).mockReturnValue(false);

// Create a properly typed mock node
const mockNode = {
id: 'node1',
type: 'test',
name: 'Test Node',
position: [0, 0] as [number, number],
typeVersion: 1,
parameters: {},
};

// Workflow with nodes and skipCredentialAutoOpen flag
const workflowWithSkipFlag = {
...EMPTY_WORKFLOW,
meta: { templateId: '2722', skipCredentialAutoOpen: true },
nodes: [mockNode],
};
workflowsStore.workflow = workflowWithSkipFlag;
workflowsStore.getNodes.mockReturnValue([mockNode]);

renderComponent();

// Modal should NOT have been opened due to skipCredentialAutoOpen flag
expect(uiStore.openModal).not.toHaveBeenCalledWith(SETUP_CREDENTIALS_MODAL_KEY);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ onBeforeUnmount(() => {
});

onMounted(() => {
if (isNewTemplatesSetupEnabled.value && showButton.value) {
const skipAutoOpen = workflowsStore.workflow?.meta?.skipCredentialAutoOpen;
if (isNewTemplatesSetupEnabled.value && showButton.value && !skipAutoOpen) {
openSetupModal();
}
});
Expand Down
Loading