mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
feat(editor): Make node credential select searchable (#12497)
This commit is contained in:
parent
b1a40a231b
commit
91277c44f1
|
@ -250,7 +250,7 @@ describe('Webhook Trigger node', () => {
|
|||
});
|
||||
// add credentials
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
|
||||
|
@ -293,7 +293,7 @@ describe('Webhook Trigger node', () => {
|
|||
});
|
||||
// add credentials
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
|
||||
|
|
|
@ -297,10 +297,9 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
workflowsPage.actions.createWorkflowFromCard();
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
|
||||
// Only the credential in this project (+ the 'Create new' option) should
|
||||
// be in the dropdown
|
||||
// Only the credential in this project should be in the dropdown
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').should('have.length', 2);
|
||||
getVisibleSelect().find('li').should('have.length', 1);
|
||||
});
|
||||
|
||||
it('should only show credentials in their personal project for members', () => {
|
||||
|
@ -325,10 +324,9 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
workflowsPage.actions.createWorkflowFromCard();
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
|
||||
// Only the own credential the shared one (+ the 'Create new' option)
|
||||
// should be in the dropdown
|
||||
// Only the own credential the shared one should be in the dropdown
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').should('have.length', 3);
|
||||
getVisibleSelect().find('li').should('have.length', 2);
|
||||
});
|
||||
|
||||
it('should only show credentials in their personal project for members if the workflow was shared with them', () => {
|
||||
|
@ -355,10 +353,9 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
workflowsPage.getters.workflowCardContent(workflowName).click();
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
|
||||
// Only the own credential the shared one (+ the 'Create new' option)
|
||||
// should be in the dropdown
|
||||
// Only the own credential the shared one should be in the dropdown
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').should('have.length', 2);
|
||||
getVisibleSelect().find('li').should('have.length', 1);
|
||||
});
|
||||
|
||||
it("should show all credentials from all personal projects the workflow's been shared into for the global owner", () => {
|
||||
|
@ -400,10 +397,9 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
workflowsPage.getters.workflowCardContent(workflowName).click();
|
||||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME, true, true);
|
||||
|
||||
// Only the personal credentials of the workflow owner and the global owner
|
||||
// should show up.
|
||||
// Only the personal credentials of the workflow owner and the global owner should show up.
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').should('have.length', 4);
|
||||
getVisibleSelect().find('li').should('have.length', 3);
|
||||
});
|
||||
|
||||
it('should show all personal credentials if the global owner owns the workflow', () => {
|
||||
|
@ -421,6 +417,6 @@ describe('Credential Usage in Cross Shared Workflows', () => {
|
|||
|
||||
// Show all personal credentials
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').should('have.have.length', 2);
|
||||
getVisibleSelect().find('li').should('have.have.length', 1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,7 +31,7 @@ function createNotionCredential() {
|
|||
workflowPage.actions.addNodeToCanvas(NOTION_NODE_NAME);
|
||||
workflowPage.actions.openNode(NOTION_NODE_NAME);
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
cy.get('body').type('{esc}');
|
||||
workflowPage.actions.deleteNode(NOTION_NODE_NAME);
|
||||
|
@ -79,7 +79,7 @@ describe('Credentials', () => {
|
|||
workflowPage.getters.canvasNodes().last().click();
|
||||
cy.get('body').type('{enter}');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2);
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
|
||||
|
@ -99,7 +99,7 @@ describe('Credentials', () => {
|
|||
cy.get('body').type('{enter}');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
// Add oAuth credentials
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2);
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
|
||||
|
@ -107,14 +107,13 @@ describe('Credentials', () => {
|
|||
cy.get('.el-message-box').find('button').contains('Close').click();
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
// Add Service account credentials
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2);
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().last().click();
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
// Both (+ the 'Create new' option) should be in the dropdown
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').should('have.length.greaterThan', 2);
|
||||
getVisibleSelect().find('li').should('have.length', 3);
|
||||
});
|
||||
|
||||
it('should correctly render required and optional credentials', () => {
|
||||
|
@ -130,13 +129,13 @@ describe('Credentials', () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().should('have.length', 2);
|
||||
|
||||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect().find('li').contains('Create New Credential').click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().first().click();
|
||||
// This one should show auth type selector
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().should('have.length', 2);
|
||||
cy.get('body').type('{esc}');
|
||||
|
||||
workflowPage.getters.nodeCredentialsSelect().last().click();
|
||||
getVisibleSelect().find('li').contains('Create New Credential').click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().last().click();
|
||||
// This one should not show auth type selector
|
||||
credentialsModal.getters.credentialsAuthTypeSelector().should('not.exist');
|
||||
});
|
||||
|
@ -148,7 +147,7 @@ describe('Credentials', () => {
|
|||
workflowPage.getters.canvasNodes().last().click();
|
||||
cy.get('body').type('{enter}');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialsAuthTypeSelector().should('not.exist');
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
workflowPage.getters
|
||||
|
@ -164,7 +163,7 @@ describe('Credentials', () => {
|
|||
workflowPage.getters.canvasNodes().last().click();
|
||||
cy.get('body').type('{enter}');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
workflowPage.getters
|
||||
.nodeCredentialsSelect()
|
||||
|
@ -189,7 +188,7 @@ describe('Credentials', () => {
|
|||
workflowPage.getters.canvasNodes().last().click();
|
||||
cy.get('body').type('{enter}');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
workflowPage.getters.nodeCredentialsEditButton().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
|
@ -232,7 +231,7 @@ describe('Credentials', () => {
|
|||
cy.getByTestId('credential-select').click();
|
||||
cy.contains('Adalo API').click();
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
workflowPage.getters.nodeCredentialsEditButton().click();
|
||||
credentialsModal.getters.credentialsEditModal().should('be.visible');
|
||||
|
@ -296,7 +295,7 @@ describe('Credentials', () => {
|
|||
|
||||
workflowPage.getters.nodeCredentialsSelect().should('exist');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.actions.fillCredentialsForm();
|
||||
workflowPage.getters
|
||||
.nodeCredentialsSelect()
|
||||
|
@ -325,7 +324,7 @@ describe('Credentials', () => {
|
|||
workflowPage.actions.addNodeToCanvas('Slack', true, true, 'Get a channel');
|
||||
workflowPage.getters.nodeCredentialsSelect().should('exist');
|
||||
workflowPage.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
|
||||
nodeDetailsView.getters.copyInput().should('not.exist');
|
||||
});
|
||||
|
|
|
@ -89,7 +89,7 @@ describe('Community and custom nodes in canvas', () => {
|
|||
workflowPage.actions.addNodeToCanvas('Manual');
|
||||
workflowPage.actions.addNodeToCanvas('E2E Node with native n8n credential', true, true);
|
||||
workflowPage.getters.nodeCredentialsLabel().click();
|
||||
cy.contains('Create New Credential').click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.editCredentialModal().should('be.visible');
|
||||
credentialsModal.getters.editCredentialModal().should('contain.text', 'Notion API');
|
||||
});
|
||||
|
@ -98,7 +98,7 @@ describe('Community and custom nodes in canvas', () => {
|
|||
workflowPage.actions.addNodeToCanvas('Manual');
|
||||
workflowPage.actions.addNodeToCanvas('E2E Node with custom credential', true, true);
|
||||
workflowPage.getters.nodeCredentialsLabel().click();
|
||||
cy.contains('Create New Credential').click();
|
||||
workflowPage.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.editCredentialModal().should('be.visible');
|
||||
credentialsModal.getters.editCredentialModal().should('contain.text', 'Custom E2E Credential');
|
||||
});
|
||||
|
|
|
@ -367,7 +367,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account project 1');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
@ -382,7 +382,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account project 1');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
@ -396,7 +396,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account project 2');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
@ -407,7 +407,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account project 2');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
@ -425,7 +425,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account personal project');
|
||||
ndv.getters.backToCanvas().click();
|
||||
|
@ -436,7 +436,7 @@ describe('Projects', { disableAutoLogin: true }, () => {
|
|||
workflowPage.getters.nodeCredentialsSelect().first().click();
|
||||
getVisibleSelect()
|
||||
.find('li')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 1)
|
||||
.first()
|
||||
.should('contain.text', 'Notion account personal project');
|
||||
});
|
||||
|
|
|
@ -5,7 +5,6 @@ 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();
|
||||
const ndv = new NDV();
|
||||
|
@ -434,7 +433,7 @@ describe('AI Assistant Credential Help', () => {
|
|||
wf.actions.addNodeToCanvas('Slack', true, true, 'Get a channel');
|
||||
wf.getters.nodeCredentialsSelect().should('exist');
|
||||
wf.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
wf.getters.nodeCredentialsCreateOption().click();
|
||||
credentialsModal.getters.credentialAuthTypeRadioButtons().first().click();
|
||||
ndv.getters.copyInput().should('not.exist');
|
||||
credentialsModal.getters.oauthConnectButton().should('have.length', 1);
|
||||
|
@ -467,7 +466,7 @@ describe('AI Assistant Credential Help', () => {
|
|||
wf.actions.addNodeToCanvas('Microsoft Outlook', true, true, 'Get a calendar');
|
||||
wf.getters.nodeCredentialsSelect().should('exist');
|
||||
wf.getters.nodeCredentialsSelect().click();
|
||||
getVisibleSelect().find('li').last().click();
|
||||
wf.getters.nodeCredentialsCreateOption().click();
|
||||
ndv.getters.copyInput().should('not.exist');
|
||||
credentialsModal.getters.oauthConnectButton().should('have.length', 1);
|
||||
credentialsModal.getters.credentialInputs().should('have.length', 1);
|
||||
|
|
|
@ -136,6 +136,12 @@ defineExpose({
|
|||
<template v-if="$slots.suffix" #suffix>
|
||||
<slot name="suffix" />
|
||||
</template>
|
||||
<template v-if="$slots.footer" #footer>
|
||||
<slot name="footer" />
|
||||
</template>
|
||||
<template v-if="$slots.empty" #empty>
|
||||
<slot name="empty" />
|
||||
</template>
|
||||
<slot></slot>
|
||||
</ElSelect>
|
||||
</div>
|
||||
|
|
|
@ -37,6 +37,8 @@
|
|||
// Danger
|
||||
--color-danger-shade-1: var(--prim-color-alt-c-shade-100);
|
||||
--color-danger: var(--prim-color-alt-c);
|
||||
--color-danger-light: var(--prim-color-alt-c-tint-150);
|
||||
--color-danger-light-2: var(--prim-color-alt-c-tint-250);
|
||||
--color-danger-tint-1: var(--prim-color-alt-c-tint-400);
|
||||
--color-danger-tint-2: var(--prim-color-alt-c-tint-450);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { describe, it } from 'vitest';
|
||||
import { fireEvent, screen } from '@testing-library/vue';
|
||||
import { screen } from '@testing-library/vue';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { createTestingPinia } from '@pinia/testing';
|
||||
import NodeCredentials from './NodeCredentials.vue';
|
||||
import type { RenderOptions } from '@/__tests__/render';
|
||||
|
@ -8,6 +9,7 @@ import { useCredentialsStore } from '@/stores/credentials.store';
|
|||
import { mockedStore } from '@/__tests__/utils';
|
||||
import type { INodeUi } from '@/Interface';
|
||||
import { useNDVStore } from '@/stores/ndv.store';
|
||||
import { useUIStore } from '../stores/ui.store';
|
||||
|
||||
const httpNode: INodeUi = {
|
||||
parameters: {
|
||||
|
@ -67,6 +69,7 @@ describe('NodeCredentials', () => {
|
|||
|
||||
const credentialsStore = mockedStore(useCredentialsStore);
|
||||
const ndvStore = mockedStore(useNDVStore);
|
||||
const uiStore = mockedStore(useUIStore);
|
||||
|
||||
beforeAll(() => {
|
||||
credentialsStore.state.credentialTypes = {
|
||||
|
@ -120,7 +123,7 @@ describe('NodeCredentials', () => {
|
|||
|
||||
const credentialsSelect = screen.getByTestId('node-credentials-select');
|
||||
|
||||
await fireEvent.click(credentialsSelect);
|
||||
await userEvent.click(credentialsSelect);
|
||||
|
||||
expect(screen.queryByText('OpenAi account')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -150,7 +153,7 @@ describe('NodeCredentials', () => {
|
|||
|
||||
const credentialsSelect = screen.getByTestId('node-credentials-select');
|
||||
|
||||
await fireEvent.click(credentialsSelect);
|
||||
await userEvent.click(credentialsSelect);
|
||||
|
||||
expect(screen.queryByText('OpenAi account')).toBeInTheDocument();
|
||||
expect(screen.queryByText('OpenAi account 2')).not.toBeInTheDocument();
|
||||
|
@ -188,9 +191,69 @@ describe('NodeCredentials', () => {
|
|||
|
||||
const credentialsSelect = screen.getByTestId('node-credentials-select');
|
||||
|
||||
await fireEvent.click(credentialsSelect);
|
||||
await userEvent.click(credentialsSelect);
|
||||
|
||||
expect(screen.queryByText('OpenAi account')).toBeInTheDocument();
|
||||
expect(screen.queryByText('OpenAi account 2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should filter available credentials in the dropdown', async () => {
|
||||
ndvStore.activeNode = httpNode;
|
||||
credentialsStore.state.credentials = {
|
||||
c8vqdPpPClh4TgIO: {
|
||||
id: 'c8vqdPpPClh4TgIO',
|
||||
name: 'OpenAi account',
|
||||
type: 'openAiApi',
|
||||
isManaged: false,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
test: {
|
||||
id: 'test',
|
||||
name: 'Test OpenAi account',
|
||||
type: 'openAiApi',
|
||||
isManaged: false,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
};
|
||||
|
||||
renderComponent();
|
||||
|
||||
const credentialsSelect = screen.getByTestId('node-credentials-select');
|
||||
|
||||
await userEvent.click(credentialsSelect);
|
||||
|
||||
expect(screen.queryByText('OpenAi account')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Test OpenAi account')).toBeInTheDocument();
|
||||
|
||||
const credentialSearch = credentialsSelect.querySelector('input') as HTMLElement;
|
||||
await userEvent.type(credentialSearch, 'test');
|
||||
|
||||
expect(screen.queryByText('OpenAi account')).not.toBeInTheDocument();
|
||||
expect(screen.queryByText('Test OpenAi account')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should open the new credential modal when clicked', async () => {
|
||||
ndvStore.activeNode = httpNode;
|
||||
credentialsStore.state.credentials = {
|
||||
c8vqdPpPClh4TgIO: {
|
||||
id: 'c8vqdPpPClh4TgIO',
|
||||
name: 'OpenAi account',
|
||||
type: 'openAiApi',
|
||||
isManaged: false,
|
||||
createdAt: '',
|
||||
updatedAt: '',
|
||||
},
|
||||
};
|
||||
|
||||
renderComponent();
|
||||
|
||||
const credentialsSelect = screen.getByTestId('node-credentials-select');
|
||||
|
||||
await userEvent.click(credentialsSelect);
|
||||
await userEvent.click(screen.getByTestId('node-credentials-select-item-new'));
|
||||
|
||||
expect(uiStore.openNewCredential).toHaveBeenCalledWith('openAiApi', true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,11 +2,12 @@
|
|||
import type { ICredentialsResponse, INodeUi, INodeUpdatePropertiesInformation } from '@/Interface';
|
||||
import {
|
||||
HTTP_REQUEST_NODE_TYPE,
|
||||
type ICredentialType,
|
||||
type INodeCredentialDescription,
|
||||
type INodeCredentialsDetails,
|
||||
type NodeParameterValueType,
|
||||
} from 'n8n-workflow';
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
||||
|
||||
import { useNodeHelpers } from '@/composables/useNodeHelpers';
|
||||
import { useToast } from '@/composables/useToast';
|
||||
|
@ -31,6 +32,7 @@ import {
|
|||
updateNodeAuthType,
|
||||
} from '@/utils/nodeTypesUtils';
|
||||
import {
|
||||
N8nIcon,
|
||||
N8nInput,
|
||||
N8nInputLabel,
|
||||
N8nOption,
|
||||
|
@ -67,7 +69,7 @@ const emit = defineEmits<{
|
|||
|
||||
const telemetry = useTelemetry();
|
||||
const i18n = useI18n();
|
||||
const NEW_CREDENTIALS_TEXT = `- ${i18n.baseText('nodeCredentials.createNew')} -`;
|
||||
const NEW_CREDENTIALS_TEXT = i18n.baseText('nodeCredentials.createNew');
|
||||
|
||||
const credentialsStore = useCredentialsStore();
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
|
@ -79,7 +81,9 @@ const nodeHelpers = useNodeHelpers();
|
|||
const toast = useToast();
|
||||
|
||||
const subscribedToCredentialType = ref('');
|
||||
const filter = ref('');
|
||||
const listeningForAuthChange = ref(false);
|
||||
const selectRefs = ref<Array<InstanceType<typeof N8nSelect>>>([]);
|
||||
|
||||
const credentialTypesNode = computed(() =>
|
||||
credentialTypesNodeDescription.value.map(
|
||||
|
@ -344,9 +348,8 @@ function onCredentialSelected(
|
|||
credentialId: string | null | undefined,
|
||||
showAuthOptions = false,
|
||||
) {
|
||||
const newCredentialOptionSelected = credentialId === NEW_CREDENTIALS_TEXT;
|
||||
if (!credentialId || newCredentialOptionSelected) {
|
||||
createNewCredential(credentialType, newCredentialOptionSelected, showAuthOptions);
|
||||
if (!credentialId) {
|
||||
createNewCredential(credentialType, false, showAuthOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -501,6 +504,20 @@ function getCredentialsFieldLabel(credentialType: INodeCredentialDescription): s
|
|||
}
|
||||
return i18n.baseText('nodeCredentials.credentialsLabel');
|
||||
}
|
||||
|
||||
function setFilter(newFilter = '') {
|
||||
filter.value = newFilter;
|
||||
}
|
||||
|
||||
function matches(needle: string, haystack: string) {
|
||||
return haystack.toLocaleLowerCase().includes(needle);
|
||||
}
|
||||
|
||||
async function onClickCreateCredential(type: ICredentialType | INodeCredentialDescription) {
|
||||
selectRefs.value.forEach((select) => select.blur());
|
||||
await nextTick();
|
||||
createNewCredential(type.name, true, showMixedCredentials(type));
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -530,16 +547,20 @@ function getCredentialsFieldLabel(credentialType: INodeCredentialDescription): s
|
|||
data-test-id="node-credentials-select"
|
||||
>
|
||||
<N8nSelect
|
||||
ref="selectRefs"
|
||||
:model-value="getSelectedId(type.name)"
|
||||
:placeholder="getSelectPlaceholder(type.name, getIssues(type.name))"
|
||||
size="small"
|
||||
filterable
|
||||
:filter-method="setFilter"
|
||||
:popper-class="$style.selectPopper"
|
||||
@update:model-value="
|
||||
(value: string) => onCredentialSelected(type.name, value, showMixedCredentials(type))
|
||||
"
|
||||
@blur="emit('blur', 'credentials')"
|
||||
>
|
||||
<N8nOption
|
||||
v-for="item in options"
|
||||
v-for="item in options.filter((o) => matches(filter, o.name))"
|
||||
:key="item.id"
|
||||
:data-test-id="`node-credentials-select-item-${item.id}`"
|
||||
:label="item.name"
|
||||
|
@ -550,13 +571,17 @@ function getCredentialsFieldLabel(credentialType: INodeCredentialDescription): s
|
|||
<N8nText size="small">{{ item.typeDisplayName }}</N8nText>
|
||||
</div>
|
||||
</N8nOption>
|
||||
<N8nOption
|
||||
:key="NEW_CREDENTIALS_TEXT"
|
||||
data-test-id="node-credentials-select-item-new"
|
||||
:value="NEW_CREDENTIALS_TEXT"
|
||||
:label="NEW_CREDENTIALS_TEXT"
|
||||
>
|
||||
</N8nOption>
|
||||
<template #empty> </template>
|
||||
<template #footer>
|
||||
<div
|
||||
data-test-id="node-credentials-select-item-new"
|
||||
:class="['clickable', $style.newCredential]"
|
||||
@click="onClickCreateCredential(type)"
|
||||
>
|
||||
<N8nIcon size="xsmall" icon="plus" />
|
||||
<N8nText bold>{{ NEW_CREDENTIALS_TEXT }}</N8nText>
|
||||
</div>
|
||||
</template>
|
||||
</N8nSelect>
|
||||
|
||||
<div v-if="getIssues(type.name).length && !hideIssues" :class="$style.warning">
|
||||
|
@ -567,7 +592,7 @@ function getCredentialsFieldLabel(credentialType: INodeCredentialDescription): s
|
|||
:items="getIssues(type.name)"
|
||||
/>
|
||||
</template>
|
||||
<font-awesome-icon icon="exclamation-triangle" />
|
||||
<N8nIcon icon="exclamation-triangle" />
|
||||
</N8nTooltip>
|
||||
</div>
|
||||
|
||||
|
@ -576,7 +601,7 @@ function getCredentialsFieldLabel(credentialType: INodeCredentialDescription): s
|
|||
:class="$style.edit"
|
||||
data-test-id="credential-edit-button"
|
||||
>
|
||||
<font-awesome-icon
|
||||
<N8nIcon
|
||||
icon="pen"
|
||||
class="clickable"
|
||||
:title="i18n.baseText('nodeCredentials.updateCredential')"
|
||||
|
@ -598,10 +623,25 @@ function getCredentialsFieldLabel(credentialType: INodeCredentialDescription): s
|
|||
}
|
||||
}
|
||||
|
||||
.selectPopper {
|
||||
:global(.el-select-dropdown__list) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:has(.newCredential:hover) :global(.hover) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
&:not(:has(li)) .newCredential {
|
||||
border-top: none;
|
||||
box-shadow: none;
|
||||
border-radius: var(--border-radius-base);
|
||||
}
|
||||
}
|
||||
|
||||
.warning {
|
||||
min-width: 20px;
|
||||
margin-left: 5px;
|
||||
color: #ff8080;
|
||||
margin-left: var(--spacing-4xs);
|
||||
color: var(--color-danger-light);
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
|
||||
|
@ -610,8 +650,7 @@ function getCredentialsFieldLabel(credentialType: INodeCredentialDescription): s
|
|||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--color-text-base);
|
||||
min-width: 20px;
|
||||
margin-left: 5px;
|
||||
margin-left: var(--spacing-3xs);
|
||||
font-size: var(--font-size-s);
|
||||
}
|
||||
|
||||
|
@ -629,4 +668,21 @@ function getCredentialsFieldLabel(credentialType: INodeCredentialDescription): s
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.newCredential {
|
||||
display: flex;
|
||||
gap: var(--spacing-3xs);
|
||||
align-items: center;
|
||||
font-weight: var(--font-weight-bold);
|
||||
padding: var(--spacing-xs) var(--spacing-m);
|
||||
background-color: var(--color-background-light);
|
||||
|
||||
border-top: var(--border-base);
|
||||
box-shadow: var(--box-shadow-light);
|
||||
clip-path: inset(-12px 0 0 0); // Only show box shadow on top
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import TitledList from '@/components/TitledList.vue';
|
||||
import { useI18n } from '@/composables/useI18n';
|
||||
import { N8nTooltip, N8nIcon } from 'n8n-design-system';
|
||||
|
||||
defineProps<{
|
||||
issues: string[];
|
||||
|
@ -11,22 +12,21 @@ const i18n = useI18n();
|
|||
|
||||
<template>
|
||||
<div v-if="issues.length" :class="$style['parameter-issues']" data-test-id="parameter-issues">
|
||||
<n8n-tooltip placement="top">
|
||||
<N8nTooltip placement="top">
|
||||
<template #content>
|
||||
<TitledList :title="`${i18n.baseText('parameterInput.issues')}:`" :items="issues" />
|
||||
</template>
|
||||
<font-awesome-icon icon="exclamation-triangle" />
|
||||
</n8n-tooltip>
|
||||
<N8nIcon icon="exclamation-triangle" />
|
||||
</N8nTooltip>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style module lang="scss">
|
||||
.parameter-issues {
|
||||
width: 20px;
|
||||
text-align: right;
|
||||
float: right;
|
||||
color: #ff8080;
|
||||
color: var(--color-danger-light);
|
||||
font-size: var(--font-size-s);
|
||||
padding-left: var(--spacing-4xs);
|
||||
padding-left: var(--spacing-3xs);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -597,7 +597,6 @@
|
|||
"credentialsList.confirmMessage.confirmButtonText": "Yes, delete",
|
||||
"credentialsList.confirmMessage.headline": "Delete Credential?",
|
||||
"credentialsList.confirmMessage.message": "Are you sure you want to delete {credentialName}?",
|
||||
"credentialsList.createNewCredential": "Create New Credential",
|
||||
"credentialsList.created": "Created",
|
||||
"credentialsList.credentials": "Credentials",
|
||||
"credentialsList.deleteCredential": "Delete Credential",
|
||||
|
@ -1187,7 +1186,7 @@
|
|||
"nodeCreator.aiPanel.workflowTriggerDescription": "Runs the flow when called by the Execute Workflow node from a different workflow",
|
||||
"nodeCreator.nodeItem.triggerIconTitle": "Trigger Node",
|
||||
"nodeCreator.nodeItem.aiIconTitle": "LangChain AI Node",
|
||||
"nodeCredentials.createNew": "Create New Credential",
|
||||
"nodeCredentials.createNew": "Create new credential",
|
||||
"nodeCredentials.credentialFor": "Credential for {credentialType}",
|
||||
"nodeCredentials.credentialsLabel": "Credential to connect with",
|
||||
"nodeCredentials.issues": "Issues",
|
||||
|
|
Loading…
Reference in a new issue