mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(editor): Add option to disable credentials check in RLC (#17381)
This commit is contained in:
@@ -103,3 +103,34 @@ export const TEST_NODE_SINGLE_MODE: INode = {
|
|||||||
options: {},
|
options: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const TEST_PARAMETER_SKIP_CREDENTIALS_CHECK: INodeProperties = {
|
||||||
|
...TEST_PARAMETER_MULTI_MODE,
|
||||||
|
name: 'testParameterSkipCredentialsCheck',
|
||||||
|
modes: [
|
||||||
|
{
|
||||||
|
displayName: 'From List',
|
||||||
|
name: 'list',
|
||||||
|
type: 'list',
|
||||||
|
typeOptions: {
|
||||||
|
searchListMethod: 'testSearch',
|
||||||
|
searchable: true,
|
||||||
|
skipCredentialsCheckInRLC: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TEST_NODE_NO_CREDENTIALS: INode = {
|
||||||
|
...TEST_NODE_MULTI_MODE,
|
||||||
|
name: 'Test Node - No Credentials',
|
||||||
|
parameters: {
|
||||||
|
authentication: undefined,
|
||||||
|
resource: 'test',
|
||||||
|
operation: 'get',
|
||||||
|
testParameterSkipCredentialsCheck: TEST_MODEL_VALUE,
|
||||||
|
id: '',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
credentials: undefined,
|
||||||
|
};
|
||||||
|
|||||||
@@ -9,9 +9,11 @@ import {
|
|||||||
TEST_MODEL_VALUE,
|
TEST_MODEL_VALUE,
|
||||||
TEST_NODE_MULTI_MODE,
|
TEST_NODE_MULTI_MODE,
|
||||||
TEST_NODE_SINGLE_MODE,
|
TEST_NODE_SINGLE_MODE,
|
||||||
|
TEST_NODE_NO_CREDENTIALS,
|
||||||
TEST_PARAMETER_ADD_RESOURCE,
|
TEST_PARAMETER_ADD_RESOURCE,
|
||||||
TEST_PARAMETER_MULTI_MODE,
|
TEST_PARAMETER_MULTI_MODE,
|
||||||
TEST_PARAMETER_SINGLE_MODE,
|
TEST_PARAMETER_SINGLE_MODE,
|
||||||
|
TEST_PARAMETER_SKIP_CREDENTIALS_CHECK,
|
||||||
} from './ResourceLocator.test.constants';
|
} from './ResourceLocator.test.constants';
|
||||||
|
|
||||||
vi.mock('vue-router', async () => {
|
vi.mock('vue-router', async () => {
|
||||||
@@ -197,6 +199,101 @@ describe('ResourceLocator', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('renders error when credentials are required and skipCredentialsCheckInRLC is false', async () => {
|
||||||
|
nodeTypesStore.getResourceLocatorResults.mockResolvedValue({
|
||||||
|
results: [
|
||||||
|
{
|
||||||
|
name: 'Test Resource',
|
||||||
|
value: 'test-resource',
|
||||||
|
url: 'https://test.com/test-resource',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
paginationToken: null,
|
||||||
|
});
|
||||||
|
nodeTypesStore.getNodeType = vi.fn().mockReturnValue({
|
||||||
|
displayName: 'Test Node',
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'testAuth',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { getByTestId, queryByTestId } = renderComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: TEST_MODEL_VALUE,
|
||||||
|
parameter: TEST_PARAMETER_MULTI_MODE,
|
||||||
|
path: `parameters.${TEST_PARAMETER_MULTI_MODE.name}`,
|
||||||
|
node: TEST_NODE_NO_CREDENTIALS,
|
||||||
|
displayTitle: 'Test Resource Locator',
|
||||||
|
expressionComputedValue: '',
|
||||||
|
isValueExpression: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getByTestId(`resource-locator-${TEST_PARAMETER_MULTI_MODE.name}`)).toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.click(getByTestId('rlc-input'));
|
||||||
|
|
||||||
|
expect(getByTestId('rlc-error-container')).toBeInTheDocument();
|
||||||
|
expect(getByTestId('permission-error-link')).toBeInTheDocument();
|
||||||
|
expect(queryByTestId('rlc-item')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders list items when skipCredentialsCheckInRLC is true even without credentials', async () => {
|
||||||
|
const TEST_ITEMS = [
|
||||||
|
{ name: 'Test Resource', value: 'test-resource', url: 'https://test.com/test-resource' },
|
||||||
|
{
|
||||||
|
name: 'Test Resource 2',
|
||||||
|
value: 'test-resource-2',
|
||||||
|
url: 'https://test.com/test-resource-2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
nodeTypesStore.getResourceLocatorResults.mockResolvedValue({
|
||||||
|
results: TEST_ITEMS,
|
||||||
|
paginationToken: null,
|
||||||
|
});
|
||||||
|
nodeTypesStore.getNodeType = vi.fn().mockReturnValue({
|
||||||
|
displayName: 'Test Node',
|
||||||
|
credentials: [
|
||||||
|
{
|
||||||
|
name: 'testAuth',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const { getByTestId, getByText, getAllByTestId, queryByTestId } = renderComponent({
|
||||||
|
props: {
|
||||||
|
modelValue: TEST_MODEL_VALUE,
|
||||||
|
parameter: TEST_PARAMETER_SKIP_CREDENTIALS_CHECK,
|
||||||
|
path: `parameters.${TEST_PARAMETER_SKIP_CREDENTIALS_CHECK.name}`,
|
||||||
|
node: TEST_NODE_NO_CREDENTIALS,
|
||||||
|
displayTitle: 'Test Resource Locator',
|
||||||
|
expressionComputedValue: '',
|
||||||
|
isValueExpression: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(
|
||||||
|
getByTestId(`resource-locator-${TEST_PARAMETER_SKIP_CREDENTIALS_CHECK.name}`),
|
||||||
|
).toBeInTheDocument();
|
||||||
|
|
||||||
|
await userEvent.click(getByTestId('rlc-input'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(nodeTypesStore.getResourceLocatorResults).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(getAllByTestId('rlc-item')).toHaveLength(TEST_ITEMS.length);
|
||||||
|
TEST_ITEMS.forEach((item) => {
|
||||||
|
expect(getByText(item.name)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
expect(queryByTestId('rlc-error-container')).not.toBeInTheDocument();
|
||||||
|
expect(queryByTestId('permission-error-link')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
// Testing error message deduplication
|
// Testing error message deduplication
|
||||||
describe('ResourceLocator credentials error handling', () => {
|
describe('ResourceLocator credentials error handling', () => {
|
||||||
it.each([
|
it.each([
|
||||||
|
|||||||
@@ -186,8 +186,9 @@ const hasCredentialError = computed(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const credentialsNotSet = computed(() => {
|
const credentialsRequiredAndNotSet = computed(() => {
|
||||||
if (!props.node) return false;
|
if (!props.node) return false;
|
||||||
|
if (skipCredentialsCheckInRLC.value) return false;
|
||||||
const nodeType = nodeTypesStore.getNodeType(props.node.type);
|
const nodeType = nodeTypesStore.getNodeType(props.node.type);
|
||||||
if (nodeType) {
|
if (nodeType) {
|
||||||
const usesCredentials = nodeType.credentials !== undefined && nodeType.credentials.length > 0;
|
const usesCredentials = nodeType.credentials !== undefined && nodeType.credentials.length > 0;
|
||||||
@@ -315,6 +316,10 @@ const currentQueryError = computed(() => {
|
|||||||
|
|
||||||
const isSearchable = computed(() => !!getPropertyArgument(currentMode.value, 'searchable'));
|
const isSearchable = computed(() => !!getPropertyArgument(currentMode.value, 'searchable'));
|
||||||
|
|
||||||
|
const skipCredentialsCheckInRLC = computed(
|
||||||
|
() => !!getPropertyArgument(currentMode.value, 'skipCredentialsCheckInRLC'),
|
||||||
|
);
|
||||||
|
|
||||||
const requiresSearchFilter = computed(
|
const requiresSearchFilter = computed(
|
||||||
() => !!getPropertyArgument(currentMode.value, 'searchFilterRequired'),
|
() => !!getPropertyArgument(currentMode.value, 'searchFilterRequired'),
|
||||||
);
|
);
|
||||||
@@ -666,7 +671,7 @@ async function loadResources() {
|
|||||||
const paramsKey = currentRequestKey.value;
|
const paramsKey = currentRequestKey.value;
|
||||||
const cachedResponse = cachedResponses.value[paramsKey];
|
const cachedResponse = cachedResponses.value[paramsKey];
|
||||||
|
|
||||||
if (credentialsNotSet.value) {
|
if (credentialsRequiredAndNotSet.value) {
|
||||||
setResponse(paramsKey, { error: true });
|
setResponse(paramsKey, { error: true });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -922,7 +927,7 @@ function removeOverride() {
|
|||||||
<template #error>
|
<template #error>
|
||||||
<div :class="$style.errorContainer" data-test-id="rlc-error-container">
|
<div :class="$style.errorContainer" data-test-id="rlc-error-container">
|
||||||
<n8n-text
|
<n8n-text
|
||||||
v-if="credentialsNotSet || currentResponse.errorDetails"
|
v-if="credentialsRequiredAndNotSet || currentResponse.errorDetails"
|
||||||
color="text-dark"
|
color="text-dark"
|
||||||
align="center"
|
align="center"
|
||||||
tag="div"
|
tag="div"
|
||||||
@@ -946,9 +951,12 @@ function removeOverride() {
|
|||||||
{{ currentResponse.errorDetails.description }}
|
{{ currentResponse.errorDetails.description }}
|
||||||
</N8nNotice>
|
</N8nNotice>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="hasCredentialError || credentialsNotSet" data-test-id="permission-error-link">
|
<div
|
||||||
|
v-if="hasCredentialError || credentialsRequiredAndNotSet"
|
||||||
|
data-test-id="permission-error-link"
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
v-if="credentialsNotSet"
|
v-if="credentialsRequiredAndNotSet"
|
||||||
:class="$style['credential-link']"
|
:class="$style['credential-link']"
|
||||||
@click="createNewCredential"
|
@click="createNewCredential"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1474,6 +1474,10 @@ export interface INodePropertyModeTypeOptions {
|
|||||||
searchListMethod?: string; // Supported by: options
|
searchListMethod?: string; // Supported by: options
|
||||||
searchFilterRequired?: boolean;
|
searchFilterRequired?: boolean;
|
||||||
searchable?: boolean;
|
searchable?: boolean;
|
||||||
|
/**
|
||||||
|
* If true, the resource locator will not show an error if the credentials are not selected
|
||||||
|
*/
|
||||||
|
skipCredentialsCheckInRLC?: boolean;
|
||||||
allowNewResource?: {
|
allowNewResource?: {
|
||||||
label: string;
|
label: string;
|
||||||
defaultName: string;
|
defaultName: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user