From 923b0d4951cd63481efabbbf43ed6854f941e947 Mon Sep 17 00:00:00 2001 From: Harsh Vador Date: Thu, 23 Apr 2026 15:20:58 +0530 Subject: [PATCH 1/5] feat(connections): add ConnectionsRouterClassBase for pluggable connection routing --- .../AddServicePage.component.tsx | 24 ++-- .../EditConnectionFormPage.component.tsx | 6 +- .../utils/ConnectionsRouterClassBase.test.ts | 135 ++++++++++++++++++ .../src/utils/ConnectionsRouterClassBase.ts | 72 ++++++++++ 4 files changed, 221 insertions(+), 16 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.test.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AddServicePage/AddServicePage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AddServicePage/AddServicePage.component.tsx index 5c6393262277..c7d2ff36c557 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/AddServicePage/AddServicePage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AddServicePage/AddServicePage.component.tsx @@ -27,7 +27,6 @@ import IngestionStepper from '../../components/Settings/Services/Ingestion/Inges import ConnectionConfigForm from '../../components/Settings/Services/ServiceConfig/ConnectionConfigForm'; import FiltersConfigForm from '../../components/Settings/Services/ServiceConfig/FiltersConfigForm'; import { AUTO_PILOT_APP_NAME } from '../../constants/Applications.constant'; -import { GlobalSettingsMenuCategory } from '../../constants/GlobalSettings.constants'; import { EXCLUDE_AUTO_PILOT_SERVICE_TYPES, SERVICE_DEFAULT_ERROR_MAP, @@ -43,16 +42,11 @@ import { getServiceLogo } from '../../utils/CommonUtils'; import { getEntityFeedLink } from '../../utils/EntityUtils'; import { handleEntityCreationError } from '../../utils/formUtils'; import { translateWithNestedKeys } from '../../utils/i18next/LocalUtil'; -import { - getAddServicePath, - getServiceDetailsPath, - getSettingPath, -} from '../../utils/RouterUtils'; +import connectionsRouterClassBase from '../../utils/ConnectionsRouterClassBase'; import serviceUtilClassBase from '../../utils/ServiceUtilClassBase'; import { getAddServiceEntityBreadcrumb, getEntityTypeFromServiceCategory, - getServiceRouteFromServiceType, getServiceType, } from '../../utils/ServiceUtils'; import { showErrorToast } from '../../utils/ToastUtils'; @@ -112,17 +106,12 @@ const AddServicePage = () => { ...prev, serviceType: '', })); - navigate(getAddServicePath(category)); + navigate(connectionsRouterClassBase.getAddServicePath(category)); }; // Select service const handleSelectServiceCancel = () => { - navigate( - getSettingPath( - GlobalSettingsMenuCategory.SERVICES, - getServiceRouteFromServiceType(serviceCategory) - ) - ); + navigate(connectionsRouterClassBase.getSettingsServicesPath()); }; const handleSelectServiceNextClick = () => { @@ -216,7 +205,12 @@ const AddServicePage = () => { }); } finally { setSaveServiceState('initial'); - navigate(getServiceDetailsPath(configData.name, serviceCategory)); + navigate( + connectionsRouterClassBase.getServiceDetailsPath( + serviceCategory, + configData.name + ) + ); } }; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/EditConnectionFormPage/EditConnectionFormPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/EditConnectionFormPage/EditConnectionFormPage.component.tsx index fb077051df7d..579dd6455533 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/EditConnectionFormPage/EditConnectionFormPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/EditConnectionFormPage/EditConnectionFormPage.component.tsx @@ -43,6 +43,7 @@ import { getServiceByFQN, patchService } from '../../rest/serviceAPI'; import { getEntityMissingError, getServiceLogo } from '../../utils/CommonUtils'; import { getEntityName } from '../../utils/EntityUtils'; import { translateWithNestedKeys } from '../../utils/i18next/LocalUtil'; +import connectionsRouterClassBase from '../../utils/ConnectionsRouterClassBase'; import { getPathByServiceFQN, getSettingPath } from '../../utils/RouterUtils'; import serviceUtilClassBase from '../../utils/ServiceUtilClassBase'; import { @@ -130,7 +131,10 @@ function EditConnectionFormPage() { }); navigate( - getPathByServiceFQN(serviceCategory as ServiceCategory, serviceFQN) + connectionsRouterClassBase.getPathByServiceFQN( + serviceCategory as ServiceCategory, + serviceFQN + ) ); } catch (error) { showErrorToast(error as AxiosError); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.test.ts new file mode 100644 index 000000000000..899bd8072e32 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.test.ts @@ -0,0 +1,135 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import connectionsRouterClassBase, { + ConnectionsRouterClassBase, +} from './ConnectionsRouterClassBase'; + +jest.mock('./RouterUtils', () => ({ + getServiceDetailsPath: (fqn: string, serviceCategory: string, tab?: string) => + tab + ? `/service/${serviceCategory}/${fqn}/${tab}` + : `/service/${serviceCategory}/${fqn}`, + getEditConnectionPath: (serviceCategory: string, fqn: string) => + `/service/${serviceCategory}/${fqn}/connection/edit-connection`, + getAddServicePath: (serviceCategory: string) => + `/${serviceCategory}/add-service`, + getPathByServiceFQN: (serviceCategory: string, fqn: string) => + `/service/${serviceCategory}/${fqn}/connection`, + getLogsViewerPath: ( + logEntityType: string, + logEntityName: string, + ingestionName: string + ) => `/logs/${logEntityType}/${logEntityName}/${ingestionName}`, +})); + +jest.mock('../constants/constants', () => ({ + ROUTES: { + SETTINGS_WITH_CATEGORY: '/settings/:settingCategory', + }, + PLACEHOLDER_SETTING_CATEGORY: ':settingCategory', +})); + +jest.mock('../constants/GlobalSettings.constants', () => ({ + GlobalSettingsMenuCategory: { + SERVICES: 'services', + }, +})); + +describe('ConnectionsRouterClassBase', () => { + let router: ConnectionsRouterClassBase; + + beforeEach(() => { + router = new ConnectionsRouterClassBase(); + }); + + describe('embeddedMode', () => { + it('setEmbeddedMode should be a no-op', () => { + router.setEmbeddedMode(true); + + expect(router.isEmbeddedMode()).toBe(false); + }); + + it('isEmbeddedMode should always return false', () => { + expect(router.isEmbeddedMode()).toBe(false); + }); + }); + + describe('getSettingsServicesPath', () => { + it('should return the settings services path', () => { + expect(router.getSettingsServicesPath()).toBe('/settings/services'); + }); + }); + + describe('getServiceDetailsPath', () => { + it('should return service details path without tab', () => { + expect(router.getServiceDetailsPath('databaseServices', 'my-db')).toBe( + '/service/databaseServices/my-db' + ); + }); + + it('should return service details path with tab', () => { + expect( + router.getServiceDetailsPath('databaseServices', 'my-db', 'connection') + ).toBe('/service/databaseServices/my-db/connection'); + }); + }); + + describe('getEditConnectionPath', () => { + it('should return the edit connection path', () => { + expect(router.getEditConnectionPath('databaseServices', 'my-db')).toBe( + '/service/databaseServices/my-db/connection/edit-connection' + ); + }); + }); + + describe('getAddServicePath', () => { + it('should return the add service path', () => { + expect(router.getAddServicePath('databaseServices')).toBe( + '/databaseServices/add-service' + ); + }); + }); + + describe('getPathByServiceFQN', () => { + it('should return service path with connection tab', () => { + expect(router.getPathByServiceFQN('databaseServices', 'my-db')).toBe( + '/service/databaseServices/my-db/connection' + ); + }); + }); + + describe('getLogsViewerPath', () => { + it('should return the logs viewer path', () => { + expect( + router.getLogsViewerPath('databaseServices', 'my-db', 'pipeline-1') + ).toBe('/logs/databaseServices/my-db/pipeline-1'); + }); + }); + + describe('singleton default export', () => { + it('default export should be an instance of ConnectionsRouterClassBase', () => { + expect(connectionsRouterClassBase).toBeInstanceOf( + ConnectionsRouterClassBase + ); + }); + + it('repeated imports should reference the same singleton', () => { + const { default: reimport } = jest.requireActual<{ + default: ConnectionsRouterClassBase; + }>('./ConnectionsRouterClassBase'); + + expect(reimport).toBe(connectionsRouterClassBase); + }); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts new file mode 100644 index 000000000000..c2abd586e7e7 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { PLACEHOLDER_SETTING_CATEGORY, ROUTES } from '../constants/constants'; +import { GlobalSettingsMenuCategory } from '../constants/GlobalSettings.constants'; +import { + getAddServicePath, + getEditConnectionPath, + getLogsViewerPath, + getPathByServiceFQN, + getServiceDetailsPath, +} from './RouterUtils'; + +class ConnectionsRouterClassBase { + public setEmbeddedMode(_flag: boolean): void { + // no-op in base; overridden in Collate + } + + public isEmbeddedMode(): boolean { + return false; + } + + public getSettingsServicesPath(): string { + return ROUTES.SETTINGS_WITH_CATEGORY.replace( + PLACEHOLDER_SETTING_CATEGORY, + GlobalSettingsMenuCategory.SERVICES + ); + } + + public getServiceDetailsPath( + serviceCategory: string, + fqn: string, + tab?: string + ): string { + return getServiceDetailsPath(fqn, serviceCategory, tab); + } + + public getEditConnectionPath(serviceCategory: string, fqn: string): string { + return getEditConnectionPath(serviceCategory, fqn); + } + + public getAddServicePath(serviceCategory: string): string { + return getAddServicePath(serviceCategory); + } + + public getPathByServiceFQN(serviceCategory: string, fqn: string): string { + return getPathByServiceFQN(serviceCategory, fqn); + } + + public getLogsViewerPath( + logEntityType: string, + logEntityName: string, + ingestionName: string + ): string { + return getLogsViewerPath(logEntityType, logEntityName, ingestionName); + } +} + +const connectionsRouterClassBase = new ConnectionsRouterClassBase(); + +export default connectionsRouterClassBase; +export { ConnectionsRouterClassBase }; From 09733cd3b3cdeba19f23f667db821c8377a40751 Mon Sep 17 00:00:00 2001 From: Harsh Vador Date: Thu, 23 Apr 2026 15:35:13 +0530 Subject: [PATCH 2/5] address gitar --- .../AddServicePage/AddServicePage.component.tsx | 2 +- .../src/utils/ConnectionsRouterClassBase.test.ts | 14 +++++++++++++- .../ui/src/utils/ConnectionsRouterClassBase.ts | 12 +++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AddServicePage/AddServicePage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AddServicePage/AddServicePage.component.tsx index c7d2ff36c557..8de0aaeb0258 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/AddServicePage/AddServicePage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AddServicePage/AddServicePage.component.tsx @@ -111,7 +111,7 @@ const AddServicePage = () => { // Select service const handleSelectServiceCancel = () => { - navigate(connectionsRouterClassBase.getSettingsServicesPath()); + navigate(connectionsRouterClassBase.getSettingsServicesPath(serviceCategory)); }; const handleSelectServiceNextClick = () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.test.ts index 899bd8072e32..d669402f3628 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.test.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.test.ts @@ -31,6 +31,12 @@ jest.mock('./RouterUtils', () => ({ logEntityName: string, ingestionName: string ) => `/logs/${logEntityType}/${logEntityName}/${ingestionName}`, + getSettingPath: (category: string, option: string) => + `/settings/${category}/${option}`, +})); + +jest.mock('./ServiceUtils', () => ({ + getServiceRouteFromServiceType: (type: string) => `${type}Route`, })); jest.mock('../constants/constants', () => ({ @@ -66,9 +72,15 @@ describe('ConnectionsRouterClassBase', () => { }); describe('getSettingsServicesPath', () => { - it('should return the settings services path', () => { + it('should return the generic settings services path when no category given', () => { expect(router.getSettingsServicesPath()).toBe('/settings/services'); }); + + it('should return the specific service-type tab path when serviceCategory given', () => { + expect(router.getSettingsServicesPath('databaseServices')).toBe( + '/settings/services/databaseServicesRoute' + ); + }); }); describe('getServiceDetailsPath', () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts index c2abd586e7e7..dcc9f16b2a55 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts @@ -19,7 +19,10 @@ import { getLogsViewerPath, getPathByServiceFQN, getServiceDetailsPath, + getSettingPath, } from './RouterUtils'; +import type { ServiceTypes } from 'Models'; +import { getServiceRouteFromServiceType } from './ServiceUtils'; class ConnectionsRouterClassBase { public setEmbeddedMode(_flag: boolean): void { @@ -30,7 +33,14 @@ class ConnectionsRouterClassBase { return false; } - public getSettingsServicesPath(): string { + public getSettingsServicesPath(serviceCategory?: string): string { + if (serviceCategory) { + return getSettingPath( + GlobalSettingsMenuCategory.SERVICES, + getServiceRouteFromServiceType(serviceCategory as ServiceTypes) + ); + } + return ROUTES.SETTINGS_WITH_CATEGORY.replace( PLACEHOLDER_SETTING_CATEGORY, GlobalSettingsMenuCategory.SERVICES From 33a04eeddbff7daa5911bd924f853e8ae632fb49 Mon Sep 17 00:00:00 2001 From: Harsh Vador Date: Thu, 23 Apr 2026 15:48:25 +0530 Subject: [PATCH 3/5] fix checkstyle --- .../src/pages/AddServicePage/AddServicePage.component.tsx | 6 ++++-- .../EditConnectionFormPage.component.tsx | 2 +- .../resources/ui/src/utils/ConnectionsRouterClassBase.ts | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/AddServicePage/AddServicePage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/AddServicePage/AddServicePage.component.tsx index 8de0aaeb0258..ddc790b919b2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/AddServicePage/AddServicePage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/AddServicePage/AddServicePage.component.tsx @@ -39,10 +39,10 @@ import { ConfigData, ServicesType } from '../../interface/service.interface'; import { triggerOnDemandApp } from '../../rest/applicationAPI'; import { postService } from '../../rest/serviceAPI'; import { getServiceLogo } from '../../utils/CommonUtils'; +import connectionsRouterClassBase from '../../utils/ConnectionsRouterClassBase'; import { getEntityFeedLink } from '../../utils/EntityUtils'; import { handleEntityCreationError } from '../../utils/formUtils'; import { translateWithNestedKeys } from '../../utils/i18next/LocalUtil'; -import connectionsRouterClassBase from '../../utils/ConnectionsRouterClassBase'; import serviceUtilClassBase from '../../utils/ServiceUtilClassBase'; import { getAddServiceEntityBreadcrumb, @@ -111,7 +111,9 @@ const AddServicePage = () => { // Select service const handleSelectServiceCancel = () => { - navigate(connectionsRouterClassBase.getSettingsServicesPath(serviceCategory)); + navigate( + connectionsRouterClassBase.getSettingsServicesPath(serviceCategory) + ); }; const handleSelectServiceNextClick = () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/EditConnectionFormPage/EditConnectionFormPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/EditConnectionFormPage/EditConnectionFormPage.component.tsx index 579dd6455533..13e592bf23b1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/EditConnectionFormPage/EditConnectionFormPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/EditConnectionFormPage/EditConnectionFormPage.component.tsx @@ -41,9 +41,9 @@ import { SearchSourceAlias } from '../../interface/search.interface'; import { ConfigData, ServicesType } from '../../interface/service.interface'; import { getServiceByFQN, patchService } from '../../rest/serviceAPI'; import { getEntityMissingError, getServiceLogo } from '../../utils/CommonUtils'; +import connectionsRouterClassBase from '../../utils/ConnectionsRouterClassBase'; import { getEntityName } from '../../utils/EntityUtils'; import { translateWithNestedKeys } from '../../utils/i18next/LocalUtil'; -import connectionsRouterClassBase from '../../utils/ConnectionsRouterClassBase'; import { getPathByServiceFQN, getSettingPath } from '../../utils/RouterUtils'; import serviceUtilClassBase from '../../utils/ServiceUtilClassBase'; import { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts index dcc9f16b2a55..65da6f8a8b5d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts @@ -11,6 +11,7 @@ * limitations under the License. */ +import type { ServiceTypes } from 'Models'; import { PLACEHOLDER_SETTING_CATEGORY, ROUTES } from '../constants/constants'; import { GlobalSettingsMenuCategory } from '../constants/GlobalSettings.constants'; import { @@ -21,7 +22,6 @@ import { getServiceDetailsPath, getSettingPath, } from './RouterUtils'; -import type { ServiceTypes } from 'Models'; import { getServiceRouteFromServiceType } from './ServiceUtils'; class ConnectionsRouterClassBase { From c20defa46572c0e7b2fa4d4cb38417901c8ab09a Mon Sep 17 00:00:00 2001 From: Harsh Vador Date: Thu, 23 Apr 2026 18:45:36 +0530 Subject: [PATCH 4/5] add edit ingestion path --- .../ui/src/utils/ConnectionsRouterClassBase.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts index 65da6f8a8b5d..684d84a6391a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts @@ -17,6 +17,7 @@ import { GlobalSettingsMenuCategory } from '../constants/GlobalSettings.constant import { getAddServicePath, getEditConnectionPath, + getEditIngestionPath, getLogsViewerPath, getPathByServiceFQN, getServiceDetailsPath, @@ -67,6 +68,20 @@ class ConnectionsRouterClassBase { return getPathByServiceFQN(serviceCategory, fqn); } + public getEditIngestionPath( + serviceCategory: string, + fqn: string, + ingestionFqn: string, + ingestionType: string + ): string { + return getEditIngestionPath( + serviceCategory, + fqn, + ingestionFqn, + ingestionType + ); + } + public getLogsViewerPath( logEntityType: string, logEntityName: string, @@ -80,3 +95,4 @@ const connectionsRouterClassBase = new ConnectionsRouterClassBase(); export default connectionsRouterClassBase; export { ConnectionsRouterClassBase }; + From 68898d87f3a02097ad21176d2eb2fa0582de99a1 Mon Sep 17 00:00:00 2001 From: Harsh Vador Date: Thu, 23 Apr 2026 18:54:33 +0530 Subject: [PATCH 5/5] fix checkstyle --- .../main/resources/ui/src/utils/ConnectionsRouterClassBase.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts index 684d84a6391a..f1460aa98c4c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts @@ -95,4 +95,3 @@ const connectionsRouterClassBase = new ConnectionsRouterClassBase(); export default connectionsRouterClassBase; export { ConnectionsRouterClassBase }; -