mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(editor): Fix updating of canvas node issue when credential is set-up (#14633)
Co-authored-by: Alex Grozav <alex@grozav.com>
This commit is contained in:
@@ -1,3 +1,7 @@
|
|||||||
|
export function getCredentialsPageUrl() {
|
||||||
|
return '/home/credentials';
|
||||||
|
}
|
||||||
|
|
||||||
export const verifyCredentialsListPageIsLoaded = () => {
|
export const verifyCredentialsListPageIsLoaded = () => {
|
||||||
cy.get('[data-test-id="resources-list-wrapper"], [data-test-id="empty-resources-list"]').should(
|
cy.get('[data-test-id="resources-list-wrapper"], [data-test-id="empty-resources-list"]').should(
|
||||||
'be.visible',
|
'be.visible',
|
||||||
@@ -8,3 +12,104 @@ export const loadCredentialsPage = (credentialsPageUrl: string) => {
|
|||||||
cy.visit(credentialsPageUrl);
|
cy.visit(credentialsPageUrl);
|
||||||
verifyCredentialsListPageIsLoaded();
|
verifyCredentialsListPageIsLoaded();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getters - Page
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getEmptyListCreateCredentialButton() {
|
||||||
|
return cy.getByTestId('empty-resources-list').find('button');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCredentialCards() {
|
||||||
|
return cy.getByTestId('resources-list-item');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getters - Modal
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function getNewCredentialModal() {
|
||||||
|
return cy.getByTestId('selectCredential-modal', { timeout: 5000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEditCredentialModal() {
|
||||||
|
return cy.getByTestId('editCredential-modal', { timeout: 5000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNewCredentialTypeSelect() {
|
||||||
|
return cy.getByTestId('new-credential-type-select');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNewCredentialTypeOption(credentialType: string) {
|
||||||
|
return cy.getByTestId('new-credential-type-select-option').contains(credentialType);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNewCredentialTypeButton() {
|
||||||
|
return cy.getByTestId('new-credential-type-button');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCredentialConnectionParameterInputs() {
|
||||||
|
return cy.getByTestId('credential-connection-parameter');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConnectionParameter(fieldName: string) {
|
||||||
|
return getCredentialConnectionParameterInputs().find(
|
||||||
|
`:contains('${fieldName}') .n8n-input input`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCredentialSaveButton() {
|
||||||
|
return cy.getByTestId('credential-save-button', { timeout: 5000 });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions - Modal
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function setCredentialName(name: string) {
|
||||||
|
cy.getByTestId('credential-name').click();
|
||||||
|
cy.getByTestId('credential-name').find('input').clear();
|
||||||
|
cy.getByTestId('credential-name').type(name);
|
||||||
|
}
|
||||||
|
export function saveCredential() {
|
||||||
|
getCredentialSaveButton()
|
||||||
|
.click({ force: true })
|
||||||
|
.within(() => {
|
||||||
|
cy.get('button').should('not.exist');
|
||||||
|
});
|
||||||
|
getCredentialSaveButton().should('have.text', 'Saved');
|
||||||
|
}
|
||||||
|
export function saveCredentialWithWait() {
|
||||||
|
cy.intercept('POST', '/rest/credentials').as('saveCredential');
|
||||||
|
saveCredential();
|
||||||
|
cy.wait('@saveCredential');
|
||||||
|
getCredentialSaveButton().should('contain.text', 'Saved');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function closeNewCredentialModal() {
|
||||||
|
getNewCredentialModal().find('.el-dialog__close').first().click();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createNewCredential(
|
||||||
|
type: string,
|
||||||
|
name: string,
|
||||||
|
parameter: string,
|
||||||
|
parameterValue: string,
|
||||||
|
closeModal = true,
|
||||||
|
) {
|
||||||
|
getEmptyListCreateCredentialButton().click();
|
||||||
|
|
||||||
|
getNewCredentialModal().should('be.visible');
|
||||||
|
getNewCredentialTypeSelect().should('be.visible');
|
||||||
|
getNewCredentialTypeOption(type).click();
|
||||||
|
|
||||||
|
getNewCredentialTypeButton().click();
|
||||||
|
getConnectionParameter(parameter).type(parameterValue);
|
||||||
|
|
||||||
|
setCredentialName(name);
|
||||||
|
saveCredential();
|
||||||
|
if (closeModal) {
|
||||||
|
getEditCredentialModal().find('.el-dialog__close').first().click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -188,6 +188,13 @@ export function getZoomToFitButton() {
|
|||||||
return cy.getByTestId('zoom-to-fit');
|
return cy.getByTestId('zoom-to-fit');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNodeIssuesByName(nodeName: string) {
|
||||||
|
return getCanvasNodes()
|
||||||
|
.filter(`:contains(${nodeName})`)
|
||||||
|
.should('have.length.greaterThan', 0)
|
||||||
|
.findChildByTestId('node-issues');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actions
|
* Actions
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { createNewCredential, getCredentialsPageUrl } from '../composables/credentialsComposables';
|
||||||
|
import {
|
||||||
|
addLanguageModelNodeToParent,
|
||||||
|
addNodeToCanvas,
|
||||||
|
getNodeIssuesByName,
|
||||||
|
} from '../composables/workflow';
|
||||||
|
import { AGENT_NODE_NAME, AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME } from '../constants';
|
||||||
|
|
||||||
|
describe('AI-716 Correctly set up agent model shows error', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.visit(getCredentialsPageUrl());
|
||||||
|
createNewCredential('OpenAi', 'OpenAI Account', 'API Key', 'sk-123', true);
|
||||||
|
cy.visit('/workflow/new');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not show error when adding a sub-node with credential set-up', () => {
|
||||||
|
addNodeToCanvas('AI Agent');
|
||||||
|
addLanguageModelNodeToParent(
|
||||||
|
AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME,
|
||||||
|
AGENT_NODE_NAME,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
cy.get('body').type('{esc}');
|
||||||
|
|
||||||
|
getNodeIssuesByName(AI_LANGUAGE_MODEL_OPENAI_CHAT_MODEL_NODE_NAME).should('have.length', 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -9,14 +9,15 @@ import { useKeybindings } from '@/composables/useKeybindings';
|
|||||||
import type { PinDataSource } from '@/composables/usePinnedData';
|
import type { PinDataSource } from '@/composables/usePinnedData';
|
||||||
import { CanvasKey } from '@/constants';
|
import { CanvasKey } from '@/constants';
|
||||||
import type { NodeCreatorOpenSource } from '@/Interface';
|
import type { NodeCreatorOpenSource } from '@/Interface';
|
||||||
import {
|
import type {
|
||||||
type CanvasConnection,
|
CanvasConnection,
|
||||||
type CanvasEventBusEvents,
|
CanvasEventBusEvents,
|
||||||
type CanvasNode,
|
CanvasNode,
|
||||||
type CanvasNodeMoveEvent,
|
CanvasNodeMoveEvent,
|
||||||
type ConnectStartEvent,
|
ConnectStartEvent,
|
||||||
CanvasNodeRenderType,
|
CanvasNodeData,
|
||||||
} from '@/types';
|
} from '@/types';
|
||||||
|
import { CanvasNodeRenderType } from '@/types';
|
||||||
import { GRID_SIZE } from '@/utils/nodeViewUtils';
|
import { GRID_SIZE } from '@/utils/nodeViewUtils';
|
||||||
import { isPresent } from '@/utils/typesUtils';
|
import { isPresent } from '@/utils/typesUtils';
|
||||||
import { useDeviceSupport } from '@n8n/composables/useDeviceSupport';
|
import { useDeviceSupport } from '@n8n/composables/useDeviceSupport';
|
||||||
@@ -583,6 +584,15 @@ function onPaneMoveEnd() {
|
|||||||
isPaneMoving.value = false;
|
isPaneMoving.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #AI-716: Due to a bug in vue-flow reactivity, the node data is not updated when the node is added
|
||||||
|
// resulting in outdated data. We use this computed property as a workaround to get the latest node data.
|
||||||
|
const nodeDataById = computed(() => {
|
||||||
|
return props.nodes.reduce<Record<string, CanvasNodeData>>((acc, node) => {
|
||||||
|
acc[node.id] = node.data as CanvasNodeData;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context menu
|
* Context menu
|
||||||
*/
|
*/
|
||||||
@@ -812,6 +822,7 @@ provide(CanvasKey, {
|
|||||||
<slot name="node" v-bind="{ nodeProps }">
|
<slot name="node" v-bind="{ nodeProps }">
|
||||||
<Node
|
<Node
|
||||||
v-bind="nodeProps"
|
v-bind="nodeProps"
|
||||||
|
:data="nodeDataById[nodeProps.id]"
|
||||||
:read-only="readOnly"
|
:read-only="readOnly"
|
||||||
:event-bus="eventBus"
|
:event-bus="eventBus"
|
||||||
:hovered="nodesHoveredById[nodeProps.id]"
|
:hovered="nodesHoveredById[nodeProps.id]"
|
||||||
|
|||||||
Reference in New Issue
Block a user