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..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 @@ -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, @@ -40,19 +39,14 @@ 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 { - getAddServicePath, - getServiceDetailsPath, - getSettingPath, -} from '../../utils/RouterUtils'; import serviceUtilClassBase from '../../utils/ServiceUtilClassBase'; import { getAddServiceEntityBreadcrumb, getEntityTypeFromServiceCategory, - getServiceRouteFromServiceType, getServiceType, } from '../../utils/ServiceUtils'; import { showErrorToast } from '../../utils/ToastUtils'; @@ -112,16 +106,13 @@ const AddServicePage = () => { ...prev, serviceType: '', })); - navigate(getAddServicePath(category)); + navigate(connectionsRouterClassBase.getAddServicePath(category)); }; // Select service const handleSelectServiceCancel = () => { navigate( - getSettingPath( - GlobalSettingsMenuCategory.SERVICES, - getServiceRouteFromServiceType(serviceCategory) - ) + connectionsRouterClassBase.getSettingsServicesPath(serviceCategory) ); }; @@ -216,7 +207,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..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,6 +41,7 @@ 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 { getPathByServiceFQN, getSettingPath } from '../../utils/RouterUtils'; @@ -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..d669402f3628 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.test.ts @@ -0,0 +1,147 @@ +/* + * 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}`, + getSettingPath: (category: string, option: string) => + `/settings/${category}/${option}`, +})); + +jest.mock('./ServiceUtils', () => ({ + getServiceRouteFromServiceType: (type: string) => `${type}Route`, +})); + +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 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', () => { + 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..f1460aa98c4c --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ConnectionsRouterClassBase.ts @@ -0,0 +1,97 @@ +/* + * 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 type { ServiceTypes } from 'Models'; +import { PLACEHOLDER_SETTING_CATEGORY, ROUTES } from '../constants/constants'; +import { GlobalSettingsMenuCategory } from '../constants/GlobalSettings.constants'; +import { + getAddServicePath, + getEditConnectionPath, + getEditIngestionPath, + getLogsViewerPath, + getPathByServiceFQN, + getServiceDetailsPath, + getSettingPath, +} from './RouterUtils'; +import { getServiceRouteFromServiceType } from './ServiceUtils'; + +class ConnectionsRouterClassBase { + public setEmbeddedMode(_flag: boolean): void { + // no-op in base; overridden in Collate + } + + public isEmbeddedMode(): boolean { + return false; + } + + 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 + ); + } + + 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 getEditIngestionPath( + serviceCategory: string, + fqn: string, + ingestionFqn: string, + ingestionType: string + ): string { + return getEditIngestionPath( + serviceCategory, + fqn, + ingestionFqn, + ingestionType + ); + } + + public getLogsViewerPath( + logEntityType: string, + logEntityName: string, + ingestionName: string + ): string { + return getLogsViewerPath(logEntityType, logEntityName, ingestionName); + } +} + +const connectionsRouterClassBase = new ConnectionsRouterClassBase(); + +export default connectionsRouterClassBase; +export { ConnectionsRouterClassBase };