diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts index dff3416925a..0b7f16cc465 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/CustomizeDetailPage.spec.ts @@ -529,4 +529,72 @@ test.describe('Persona customization', () => { }); }); }); + + test('Validate Glossary Term details page after customization of tabs', async ({ + adminPage, + userPage, + }) => { + test.slow(); + + await test.step('apply customization', async () => { + await settingClick(adminPage, GlobalSettingOptions.PERSONA); + await adminPage.waitForLoadState('networkidle'); + await adminPage + .getByTestId(`persona-details-card-${persona.data.name}`) + .click(); + await adminPage.getByRole('tab', { name: 'Customize UI' }).click(); + await adminPage.waitForLoadState('networkidle'); + await adminPage.getByText('Governance').click(); + await adminPage.getByText('Glossary Term', { exact: true }).click(); + + await adminPage.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + const dragElement = adminPage.getByTestId('tab-Overview'); + const dropTarget = adminPage.getByTestId('tab-Custom Properties'); + + await dragElement.dragTo(dropTarget); + + expect(adminPage.getByTestId('save-button')).toBeEnabled(); + + await adminPage.getByTestId('save-button').click(); + + await toastNotification( + adminPage, + /^Page layout (created|updated) successfully\.$/ + ); + }); + + await test.step('Validate customization', async () => { + await redirectToHomePage(userPage); + + const entity = getCustomizeDetailsEntity( + ECustomizedGovernance.GLOSSARY_TERM + ); + await entity.visitEntityPage(userPage); + await userPage.waitForLoadState('networkidle'); + await userPage.waitForSelector('[data-testid="loader"]', { + state: 'detached', + }); + + expect(userPage.getByRole('tab', { name: 'Overview' })).toBeVisible(); + expect( + userPage.getByRole('tab', { name: 'Glossary Terms' }) + ).toBeVisible(); + expect( + userPage.getByTestId('create-error-placeholder-Glossary Term') + ).toBeVisible(); + + await userPage.getByRole('tab', { name: 'Overview' }).click(); + + expect(userPage.getByTestId('asset-description-container')).toBeVisible(); + + await userPage.getByRole('tab', { name: 'Glossary Terms' }).click(); + + expect( + userPage.getByTestId('create-error-placeholder-Glossary Term') + ).toBeVisible(); + }); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Customization/GenericProvider/GenericProvider.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Customization/GenericProvider/GenericProvider.tsx index 620bc33d56a..a839cfdb3f5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Customization/GenericProvider/GenericProvider.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Customization/GenericProvider/GenericProvider.tsx @@ -98,7 +98,7 @@ export const GenericProvider = >({ const { tab } = useRequiredParams<{ tab: EntityTabs }>(); const expandedLayout = useRef([]); const [layout, setLayout] = useState( - getLayoutFromCustomizedPage(pageType, tab, customizedPage) + getLayoutFromCustomizedPage(pageType, tab, customizedPage, isVersionView) ); const [filteredKeys, setFilteredKeys] = useState([]); const [activeTagDropdownKey, setActiveTagDropdownKey] = useState< @@ -106,8 +106,10 @@ export const GenericProvider = >({ >(null); useEffect(() => { - setLayout(getLayoutFromCustomizedPage(pageType, tab, customizedPage)); - }, [customizedPage, tab, pageType]); + setLayout( + getLayoutFromCustomizedPage(pageType, tab, customizedPage, isVersionView) + ); + }, [customizedPage, tab, pageType, isVersionView]); const onThreadPanelClose = useCallback(() => { setThreadLink(''); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Customization/GenericTab/generic-tab.less b/openmetadata-ui/src/main/resources/ui/src/components/Customization/GenericTab/generic-tab.less index 0f25a58fe9b..3b48dd90256 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Customization/GenericTab/generic-tab.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/Customization/GenericTab/generic-tab.less @@ -34,7 +34,7 @@ &.custom-tab { background: @white; border-radius: @border-rad-sm; - margin-top: @padding-sm; + padding: @padding-sm; } > #KnowledgePanel\.LeftPanel { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryDetails/GlossaryDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryDetails/GlossaryDetails.component.tsx index 1bbdd94d531..db06008ffe7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryDetails/GlossaryDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryDetails/GlossaryDetails.component.tsx @@ -56,8 +56,7 @@ const GlossaryDetails = ({ const { onAddGlossaryTerm } = useGlossaryStore(); // Since we are rendering this component for all customized tabs we need tab ID to get layout form store - const { tab: activeTab = EntityTabs.TERMS } = - useRequiredParams<{ tab: EntityTabs }>(); + const { tab: activeTab } = useRequiredParams<{ tab: EntityTabs }>(); const { customizedPage, isLoading } = useCustomPages(PageType.Glossary); const handleFeedCount = useCallback((data: FeedCounts) => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/GlossaryTermsV1.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/GlossaryTermsV1.component.tsx index 6edb630d492..b465cc18f1a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/GlossaryTermsV1.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/GlossaryTermsV1.component.tsx @@ -12,7 +12,6 @@ */ import { Col, Row, Tabs } from 'antd'; - import { isEmpty } from 'lodash'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -158,7 +157,7 @@ const GlossaryTermsV1 = ({ const handleAssetSave = useCallback(() => { fetchGlossaryTermAssets(); assetTabRef.current?.refreshAssets(); - activeTab !== 'assets' && activeTabHandler('assets'); + activeTab !== EntityTabs.ASSETS && activeTabHandler(EntityTabs.ASSETS); }, [assetTabRef, activeTab]); const onExtensionUpdate = useCallback( @@ -204,12 +203,12 @@ const GlossaryTermsV1 = ({ {getCountBadge( childGlossaryTerms.length, '', - activeTab === EntityTabs.TERMS + activeTab === EntityTabs.GLOSSARY_TERMS )} ), - key: EntityTabs.TERMS, + key: EntityTabs.GLOSSARY_TERMS, children: ( ({ ...jest.requireActual('react-router-dom'), - useParams: jest.fn().mockImplementation(() => ({ - glossaryName: 'glossary', - tab: 'terms', + useNavigate: jest.fn().mockImplementation(() => mockPush), +})); + +jest.mock('../../../utils/useRequiredParams', () => ({ + useRequiredParams: jest.fn().mockReturnValue({ + tab: undefined, version: 'glossaryVersion', - })), - useNavigate: jest.fn().mockReturnValue(jest.fn()), + }), +})); + +jest.mock('../../../hooks/useFqn', () => ({ + useFqn: jest.fn().mockReturnValue({ fqn: 'glossaryTerm' }), })); jest.mock( @@ -54,9 +64,9 @@ jest.mock('../GlossaryTermTab/GlossaryTermTab.component', () => jest.mock('../GlossaryHeader/GlossaryHeader.component', () => jest.fn().mockReturnValue(
GlossaryHeader.component
) ); -jest.mock('../../Customization/GenericTab/GenericTab', () => - jest.fn().mockReturnValue(
GenericTab
) -); +jest.mock('../../Customization/GenericTab/GenericTab', () => ({ + GenericTab: jest.fn().mockImplementation(() =>
GenericTab
), +})); const mockProps = { isSummaryPanelOpen: false, @@ -103,7 +113,32 @@ jest.mock('../../../utils/TableColumn.util', () => ({ })); describe('Test Glossary-term component', () => { - it('Should render GenericTab component', async () => { + it('Should render overview tab when activeTab is undefined', async () => { + render(); + + expect(screen.getByTestId('glossary-term')).toBeInTheDocument(); + + const tabs = await screen.findAllByRole('tab'); + + expect(tabs).toHaveLength(5); + expect(tabs[0].textContent).toBe('label.overview'); + + tabs + .filter((tab) => tab.textContent !== 'label.overview') + .forEach((tab) => { + expect(tab).not.toHaveAttribute('aria-selected', 'true'); + }); + + expect(mockPush).not.toHaveBeenCalled(); + }); + + it('Should render GlossaryTermTab component', async () => { + const useRequiredParamsMock = useRequiredParams as jest.Mock; + useRequiredParamsMock.mockReturnValue({ + tab: EntityTabs.GLOSSARY_TERMS, + version: 'glossaryVersion', + }); + render(); const tabs = await screen.findAllByRole('tab'); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryV1.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryV1.component.tsx index 5fd0d35b1e8..30b65ba821a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryV1.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryV1.component.tsx @@ -227,11 +227,11 @@ const GlossaryV1 = ({ setTermsLoading(true); // Update store with newly created term insertNewGlossaryTermToChildTerms(term); - if (!isGlossaryActive && tab !== 'terms') { + if (!isGlossaryActive && tab !== EntityTabs.GLOSSARY_TERMS) { navigate( getGlossaryTermDetailsPath( selectedData.fullyQualifiedName || '', - EntityTabs.TERMS + EntityTabs.GLOSSARY_TERMS ) ); } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CustomizePage/CustomizePageUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/CustomizePage/CustomizePageUtils.ts index f66b0e15d6b..08796c6c3b4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CustomizePage/CustomizePageUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CustomizePage/CustomizePageUtils.ts @@ -524,9 +524,10 @@ export const getDetailsTabWithNewLabel = ( NonNullable[number] & { isHidden?: boolean } >, customizedTabs?: Tab[], - defaultTabId: EntityTabs = EntityTabs.OVERVIEW + defaultTabId: EntityTabs = EntityTabs.OVERVIEW, + isVersionView = false ) => { - if (!customizedTabs) { + if (!customizedTabs || isVersionView) { return defaultTabs.filter((data) => !data.isHidden); } const overviewTab = defaultTabs?.find((t) => t.key === defaultTabId); @@ -571,9 +572,10 @@ export const asyncNoop = async () => { export const getLayoutFromCustomizedPage = ( pageType: PageType, tab: EntityTabs, - customizedPage?: Page | null + customizedPage?: Page | null, + isVersionView = false ) => { - if (!customizedPage) { + if (!customizedPage || isVersionView) { return getDefaultWidgetForTab(pageType, tab); }