Browse Source

Merge pull request #9717 from nocodb/nc-fix/pg-test-connection

Test connection improvements
pull/9726/head
Pranav C 1 month ago committed by GitHub
parent
commit
8eb4ed3d01
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue
  2. 2
      packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue
  3. 51
      packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue
  4. 59
      packages/nc-gui/utils/baseCreateUtils.ts
  5. 4
      packages/nocodb/src/db/util/Result.ts
  6. 6
      packages/nocodb/src/filters/global-exception/global-exception.filter.ts
  7. 16
      packages/nocodb/src/helpers/catchError.ts

2
packages/nc-gui/components/dashboard/settings/data-sources/CreateBase.vue

@ -470,7 +470,7 @@ const isIntgrationDisabled = (integration: IntegrationType = {}) => {
:disabled="!selectedIntegration || isLoading"
:loading="testingConnection"
icon-position="right"
@click="testConnection"
@click="testConnection()"
>
<template #icon>
<GeneralIcon v-if="testSuccess" icon="circleCheckSolid" class="!text-green-700 w-4 h-4" />

2
packages/nc-gui/components/dashboard/settings/data-sources/EditBase.vue

@ -626,7 +626,7 @@ function handleAutoScroll(scroll: boolean, className: string) {
:loading="testingConnection"
:disabled="isLoading"
icon-position="right"
@click="testConnection"
@click="testConnection()"
>
<template #icon>
<GeneralIcon v-if="testSuccess" icon="circleCheckSolid" class="!text-green-700 w-4 h-4" />

51
packages/nc-gui/components/workspace/integrations/forms/EditOrAddDatabase.vue

@ -303,14 +303,30 @@ const createOrUpdateIntegration = async () => {
}
}
// apply fix to config
function applyConfigFix(fix: any) {
if (!fix) return
formState.value.dataSource = {
...formState.value.dataSource,
...fix,
connection: {
...formState.value.dataSource.connection,
...fix.connection,
},
}
}
const testConnectionError = ref()
const testConnection = async () => {
const testConnection = async (retry = 0, initialConfig = null) => {
try {
await validate()
} catch (e) {
focusInvalidInput()
return
if (e.errorFields?.length) {
focusInvalidInput()
return
}
}
$e('a:source:create:extdb:test-connection', [])
@ -343,11 +359,36 @@ const testConnection = async () => {
}
}
} catch (e: any) {
// take a copy of the current config
const config = initialConfig || JSON.parse(JSON.stringify(formState.value.dataSource))
await handleConnectionError(e, retry, config)
} finally {
testingConnection.value = false
}
}
const MAX_CONNECTION_RETRIES = 3
async function handleConnectionError(e: any, retry: number, initialConfig: any): Promise<void> {
if (retry >= MAX_CONNECTION_RETRIES) {
testSuccess.value = false
testConnectionError.value = await extractSdkResponseErrorMsg(e)
// reset the connection config to initial state
formState.value.dataSource = initialConfig
return
}
const fix = generateConfigFix(e)
if (fix) {
applyConfigFix(fix)
// Retry the connection after applying the fix
return testConnection(retry + 1, initialConfig)
}
testingConnection.value = false
// If no fix is available, or fix did not resolve the issue
testSuccess.value = false
testConnectionError.value = await extractSdkResponseErrorMsg(e)
}
const handleImportURL = async () => {
@ -545,7 +586,7 @@ watch(
:loading="testingConnection"
:disabled="isLoading"
icon-position="right"
@click="testConnection"
@click="testConnection()"
>
<template #icon>
<GeneralIcon v-if="testSuccess" icon="circleCheckSolid" class="!text-green-700 w-4 h-4" />

59
packages/nc-gui/utils/baseCreateUtils.ts

@ -223,4 +223,61 @@ enum CertTypes {
key = 'key',
}
export { SSLUsage, CertTypes, ProjectCreateForm, DefaultConnection, SQLiteConnection, SnowflakeConnection, DatabricksConnection }
const errorHandlers = [
{
messages: ['unable to get local issuer certificate', 'self signed certificate in certificate chain'],
codes: ['UNABLE_TO_GET_ISSUER_CERT_LOCALLY', 'SELF_SIGNED_CERT_IN_CHAIN'],
action: {
connection: {
ssl: {
rejectUnauthorized: false,
},
},
},
},
{
messages: ['SSL is required'],
codes: ['28000'], // PostgreSQL error code for invalid authorization specification
action: {
connection: {
ssl: true,
},
},
},
{
messages: ['the server does not support SSL connections'],
codes: ['08P01'], // PostgreSQL error code for protocol violation
action: {
connection: {
ssl: false,
},
},
},
]
function generateConfigFix(e: any) {
for (const handler of errorHandlers) {
const errorMessage = e?.response?.data?.msg
const errorCode = e?.response?.data?.sql_code
if (!errorMessage && !errorCode) return
const messageMatches = errorMessage && handler.messages.some((msg) => errorMessage?.includes?.(msg))
const codeMatches = errorCode && handler.codes.includes(errorCode)
if (messageMatches || codeMatches) {
return handler.action
}
}
}
export {
generateConfigFix,
SSLUsage,
CertTypes,
ProjectCreateForm,
DefaultConnection,
SQLiteConnection,
SnowflakeConnection,
DatabricksConnection,
}

4
packages/nocodb/src/db/util/Result.ts

@ -1,11 +1,13 @@
export default class Result<T = any> {
public code: any;
public sql_code?: string;
public message: any;
public data: T;
public object: any;
constructor(code = 0, message = '', data: T | any = {}) {
constructor(code = 0, message = '', data: T | any = {}, sql_code?: string) {
this.code = code;
this.message = message;
this.data = data;
this.sql_code = sql_code;
}
}

6
packages/nocodb/src/filters/global-exception/global-exception.filter.ts

@ -17,6 +17,7 @@ import {
NcBaseErrorv2,
NotFound,
SsoError,
TestConnectionError,
Unauthorized,
UnprocessableEntity,
} from '~/helpers/catchError';
@ -58,6 +59,7 @@ export class GlobalExceptionFilter implements ExceptionFilter {
exception instanceof Forbidden ||
exception instanceof NotFound ||
exception instanceof UnprocessableEntity ||
exception instanceof TestConnectionError ||
exception instanceof SsoError ||
exception instanceof NotFoundException ||
exception instanceof ThrottlerException ||
@ -185,6 +187,10 @@ export class GlobalExceptionFilter implements ExceptionFilter {
.json({ msg: exception.message, errors: exception.errors });
} else if (exception instanceof UnprocessableEntity) {
return response.status(422).json({ msg: exception.message });
} else if (exception instanceof TestConnectionError) {
return response
.status(422)
.json({ msg: exception.message, sql_code: exception.sql_code });
} else if (exception instanceof NcBaseErrorv2) {
return response.status(exception.code).json({
error: exception.error,

16
packages/nocodb/src/helpers/catchError.ts

@ -23,6 +23,7 @@ export function extractDBError(error): {
message: string;
error: string;
details?: any;
code?: string;
} | void {
if (!error.code) return;
@ -448,6 +449,7 @@ export function extractDBError(error): {
return {
error: NcErrorType.DATABASE_ERROR,
message,
code: error.code,
};
}
}
@ -488,6 +490,15 @@ export class ExternalTimeout extends ExternalError {}
export class UnprocessableEntity extends NcBaseError {}
export class TestConnectionError extends NcBaseError {
public sql_code?: string;
constructor(message: string, sql_code?: string) {
super(message);
this.sql_code = sql_code;
}
}
export class AjvError extends NcBaseError {
constructor(param: { message: string; errors: ErrorObject[] }) {
super(param.message);
@ -726,6 +737,7 @@ export class NcError {
},
});
}
static authenticationRequired(args?: NcErrorArgs) {
throw new NcBaseErrorv2(NcErrorType.AUTHENTICATION_REQUIRED, args);
}
@ -949,6 +961,10 @@ export class NcError {
throw new UnprocessableEntity(message);
}
static testConnectionError(message = 'Unprocessable entity', code?: string) {
throw new TestConnectionError(message, code);
}
static notAllowed(message = 'Not allowed') {
throw new NotAllowed(message);
}

Loading…
Cancel
Save