diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataProduct/DataProductListPage.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataProduct/DataProductListPage.tsx index 87f53bac40dd..198121bdbf6c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataProduct/DataProductListPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataProduct/DataProductListPage.tsx @@ -23,7 +23,14 @@ import { import { Globe01, Package, Plus } from '@untitledui/icons'; import classNames from 'classnames'; import { isEmpty } from 'lodash'; -import { FC, ReactNode, useCallback, useMemo, useState } from 'react'; +import { + FC, + MouseEvent, + ReactNode, + useCallback, + useMemo, + useState, +} from 'react'; import { useTranslation } from 'react-i18next'; import { NO_DATA, ROUTES } from '../../constants/constants'; import { LEARNING_PAGE_IDS } from '../../constants/Learning.constants'; @@ -168,12 +175,18 @@ const DataProductListPage = ({ entity.name && entity.displayName !== entity.name; + const handleNameClick = (event: MouseEvent) => { + event.stopPropagation(); + dataProductListing.actionHandlers.onEntityClick?.(entity); + }; + return ( + gap={3} + onClick={handleNameClick}> { const { renderDomainCard } = useDomainCardTemplates(); const { columns: domainColumns, renderCell: renderDomainCell } = - useDomainTableColumns(); + useDomainTableColumns({ + onEntityClick: domainListing.actionHandlers.onEntityClick, + }); const selectedDomainEntities = useMemo( () => diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/atoms/domain/ui/domainFieldRenderers.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/atoms/domain/ui/domainFieldRenderers.test.tsx new file mode 100644 index 000000000000..cd0ee0a389d7 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/atoms/domain/ui/domainFieldRenderers.test.tsx @@ -0,0 +1,79 @@ +/* + * Copyright 2024 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 { fireEvent, render, screen } from '@testing-library/react'; +import { ReactNode } from 'react'; +import { Domain } from '../../../../../generated/entity/domains/domain'; +import { renderDomainNameCell } from './domainFieldRenderers'; + +jest.mock('@openmetadata/ui-core-components', () => ({ + Avatar: () => , + Box: ({ + children, + onClick, + }: { + children: ReactNode; + onClick?: () => void; + }) => ( +
+ {children} +
+ ), + Typography: ({ children }: { children: ReactNode }) => ( + {children} + ), +})); + +jest.mock('../../../../../utils/TooltipUtils', () => ({ + renderBreakableTooltip: (value: string) => value, +})); + +const DOMAIN = { + id: 'domain-id', + name: 'engineering', + displayName: 'Engineering', + fullyQualifiedName: 'engineering', +} as Domain; + +describe('renderDomainNameCell', () => { + it('navigates once when the name cell is clicked', () => { + const onClick = jest.fn(); + + render(<>{renderDomainNameCell(DOMAIN, onClick)}); + fireEvent.click(screen.getByText('Engineering')); + + expect(onClick).toHaveBeenCalledTimes(1); + }); + + it('stops the click from bubbling to the row so navigation is not duplicated', () => { + const onClick = jest.fn(); + const rowClick = jest.fn(); + + render( +
{renderDomainNameCell(DOMAIN, onClick)}
+ ); + fireEvent.click(screen.getByText('Engineering')); + + expect(onClick).toHaveBeenCalledTimes(1); + expect(rowClick).not.toHaveBeenCalled(); + }); + + it('does not attach a click handler when no onClick is provided', () => { + const rowClick = jest.fn(); + + render(
{renderDomainNameCell(DOMAIN)}
); + fireEvent.click(screen.getByText('Engineering')); + + // With no cell handler the click falls through to the row unchanged. + expect(rowClick).toHaveBeenCalledTimes(1); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/atoms/domain/ui/domainFieldRenderers.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/atoms/domain/ui/domainFieldRenderers.tsx index f8f779d318f7..86ddbcd3dd15 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/atoms/domain/ui/domainFieldRenderers.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/atoms/domain/ui/domainFieldRenderers.tsx @@ -12,7 +12,7 @@ */ import { Avatar, Box, Typography } from '@openmetadata/ui-core-components'; -import { ReactNode } from 'react'; +import { MouseEvent, ReactNode } from 'react'; import { NO_DATA } from '../../../../../constants/constants'; import { DataProduct } from '../../../../../generated/entity/domains/dataProduct'; import { Domain } from '../../../../../generated/entity/domains/domain'; @@ -69,16 +69,23 @@ export const LIST_EMPTY_STATE_CLASS = 'tw:flex tw:flex-1 tw:min-h-60 tw:items-center tw:justify-center'; export const renderDomainNameCell = ( - entity: Domain | DataProduct + entity: Domain | DataProduct, + onClick?: () => void ): ReactNode => { const entityName = getEntityName(entity); + const handleNameClick = (event: MouseEvent) => { + event.stopPropagation(); + onClick?.(); + }; + return ( + gap={3} + onClick={onClick ? handleNameClick : undefined}> ({ + useTranslation: () => ({ t: (key: string) => key }), +})); + +jest.mock('@openmetadata/ui-core-components', () => ({ + Avatar: () => , + Box: ({ + children, + onClick, + }: { + children: ReactNode; + onClick?: () => void; + }) => ( +
+ {children} +
+ ), + Typography: ({ children }: { children: ReactNode }) => ( + {children} + ), +})); + +jest.mock('../../../../../utils/TooltipUtils', () => ({ + renderBreakableTooltip: (value: string) => value, +})); + +const DOMAIN = { + id: 'domain-id', + name: 'engineering', + displayName: 'Engineering', + fullyQualifiedName: 'engineering', +} as Domain; + +describe('useDomainTableColumns', () => { + it('routes a name-cell click to onEntityClick with the row entity', () => { + const onEntityClick = jest.fn(); + + const { result } = renderHook(() => + useDomainTableColumns({ onEntityClick }) + ); + + render(<>{result.current.renderCell(DOMAIN, 'name')}); + fireEvent.click(screen.getByText('Engineering')); + + expect(onEntityClick).toHaveBeenCalledTimes(1); + expect(onEntityClick).toHaveBeenCalledWith(DOMAIN); + }); + + it('renders the name cell without a click handler when onEntityClick is omitted', () => { + const rowClick = jest.fn(); + + const { result } = renderHook(() => useDomainTableColumns()); + + render( +
{result.current.renderCell(DOMAIN, 'name')}
+ ); + fireEvent.click(screen.getByText('Engineering')); + + expect(rowClick).toHaveBeenCalledTimes(1); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/atoms/domain/ui/useDomainTableColumns.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/atoms/domain/ui/useDomainTableColumns.tsx index 8eb4971fe339..d96e23a42991 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/atoms/domain/ui/useDomainTableColumns.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/atoms/domain/ui/useDomainTableColumns.tsx @@ -26,11 +26,13 @@ import { interface UseDomainTableColumnsOptions { nameLabelKey?: string; tagSize?: 'sm' | 'lg'; + onEntityClick?: (entity: Domain) => void; } export const useDomainTableColumns = ({ nameLabelKey = 'label.domain', tagSize = 'sm', + onEntityClick, }: UseDomainTableColumnsOptions = {}) => { const { t } = useTranslation(); @@ -49,7 +51,10 @@ export const useDomainTableColumns = ({ (entity: Domain, columnId: string): ReactNode => { switch (columnId) { case 'name': - return renderDomainNameCell(entity); + return renderDomainNameCell( + entity, + onEntityClick ? () => onEntityClick(entity) : undefined + ); case 'domainType': return renderDomainTypeCell(entity); case 'owners': @@ -62,7 +67,7 @@ export const useDomainTableColumns = ({ return null; } }, - [tagSize] + [tagSize, onEntityClick] ); return { columns, renderCell };