fix(ui): Fix duplicate owners & tier field in project explorer card (#24297)

* fix(ui): fix duplicate owner field in overview section in explore card

* nit

* nit

* hide tier row in overview section

* refactor code
This commit is contained in:
Harsh Vador 2025-11-15 16:29:13 +05:30 committed by GitHub
parent e18c848851
commit 7745cc1330
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 187 additions and 58 deletions

View File

@ -26,4 +26,5 @@ export interface CommonEntitySummaryInfoV1Props {
entityInfo: EntityInfoItemV1[];
componentType: string;
isDomainVisible?: boolean;
excludedItems?: string[];
}

View File

@ -12,6 +12,7 @@
*/
import { render, screen } from '@testing-library/react';
import CommonEntitySummaryInfoV1 from './CommonEntitySummaryInfoV1';
import { EntityInfoItemV1 } from './CommonEntitySummaryInfoV1.interface';
// Mock i18n
jest.mock('react-i18next', () => ({
@ -38,7 +39,7 @@ jest.mock('react-router-dom', () => ({
),
}));
const defaultItems = [
const defaultItems: EntityInfoItemV1[] = [
{ name: 'Type', value: 'Table', visible: ['explore'] },
{ name: 'Rows', value: 1000, visible: ['explore'] },
{ name: 'Columns', value: 15, visible: ['explore'] },
@ -53,7 +54,7 @@ describe('CommonEntitySummaryInfoV1', () => {
const { container } = render(
<CommonEntitySummaryInfoV1
componentType="explore"
entityInfo={defaultItems as any}
entityInfo={defaultItems}
/>
);
@ -72,16 +73,13 @@ describe('CommonEntitySummaryInfoV1', () => {
});
it('filters out items not visible for the componentType', () => {
const items = [
const items: EntityInfoItemV1[] = [
{ name: 'Visible', value: 'Yes', visible: ['explore'] },
{ name: 'Hidden', value: 'No', visible: ['other'] },
];
render(
<CommonEntitySummaryInfoV1
componentType="explore"
entityInfo={items as any}
/>
<CommonEntitySummaryInfoV1 componentType="explore" entityInfo={items} />
);
expect(screen.getByTestId('Visible-label')).toBeInTheDocument();
@ -89,7 +87,7 @@ describe('CommonEntitySummaryInfoV1', () => {
});
it('shows domain item when isDomainVisible is true regardless of visibility array', () => {
const items = [
const items: EntityInfoItemV1[] = [
{ name: 'label.domain-plural', value: 'Domain A', visible: ['other'] },
];
@ -97,7 +95,7 @@ describe('CommonEntitySummaryInfoV1', () => {
<CommonEntitySummaryInfoV1
isDomainVisible
componentType="explore"
entityInfo={items as any}
entityInfo={items}
/>
);
@ -108,7 +106,7 @@ describe('CommonEntitySummaryInfoV1', () => {
});
it('renders internal link when isLink is true and isExternal is false', () => {
const items = [
const items: EntityInfoItemV1[] = [
{
name: 'Docs',
value: 'OpenMetadata',
@ -120,10 +118,7 @@ describe('CommonEntitySummaryInfoV1', () => {
];
render(
<CommonEntitySummaryInfoV1
componentType="explore"
entityInfo={items as any}
/>
<CommonEntitySummaryInfoV1 componentType="explore" entityInfo={items} />
);
const link = screen.getByTestId('internal-link');
@ -134,7 +129,7 @@ describe('CommonEntitySummaryInfoV1', () => {
});
it('renders external link with icon when isExternal is true', () => {
const items = [
const items: EntityInfoItemV1[] = [
{
name: 'Website',
value: 'OpenMetadata',
@ -146,10 +141,7 @@ describe('CommonEntitySummaryInfoV1', () => {
];
const { container } = render(
<CommonEntitySummaryInfoV1
componentType="explore"
entityInfo={items as any}
/>
<CommonEntitySummaryInfoV1 componentType="explore" entityInfo={items} />
);
const anchor = container.querySelector('a.summary-item-link');
@ -160,16 +152,13 @@ describe('CommonEntitySummaryInfoV1', () => {
});
it('renders dash when value is null or undefined', () => {
const items = [
const items: EntityInfoItemV1[] = [
{ name: 'Empty', value: undefined, visible: ['explore'] },
{ name: 'AlsoEmpty', value: null, visible: ['explore'] },
];
const { getByTestId } = render(
<CommonEntitySummaryInfoV1
componentType="explore"
entityInfo={items as any}
/>
<CommonEntitySummaryInfoV1 componentType="explore" entityInfo={items} />
);
expect(getByTestId('Empty-value')).toHaveTextContent('-');
@ -177,16 +166,13 @@ describe('CommonEntitySummaryInfoV1', () => {
});
it('supports labels with special characters in testids', () => {
const items = [
const items: EntityInfoItemV1[] = [
{ name: 'Label & Value', value: 'Test', visible: ['explore'] },
{ name: 'Label < > " \'', value: 'Again', visible: ['explore'] },
];
render(
<CommonEntitySummaryInfoV1
componentType="explore"
entityInfo={items as any}
/>
<CommonEntitySummaryInfoV1 componentType="explore" entityInfo={items} />
);
expect(screen.getByTestId('Label & Value-value')).toHaveTextContent('Test');
@ -194,4 +180,99 @@ describe('CommonEntitySummaryInfoV1', () => {
'Again'
);
});
it('excludes items specified in excludedItems prop', () => {
const items: EntityInfoItemV1[] = [
{ name: 'Type', value: 'Table', visible: ['explore'] },
{ name: 'Owners', value: 'John Doe', visible: ['explore'] },
{ name: 'Tier', value: 'Gold', visible: ['explore'] },
{ name: 'Rows', value: 1000, visible: ['explore'] },
];
render(
<CommonEntitySummaryInfoV1
componentType="explore"
entityInfo={items}
excludedItems={['Owners', 'Tier']}
/>
);
expect(screen.getByTestId('Type-label')).toBeInTheDocument();
expect(screen.getByTestId('Rows-label')).toBeInTheDocument();
expect(screen.queryByTestId('Owners-label')).toBeNull();
expect(screen.queryByTestId('Tier-label')).toBeNull();
});
it('renders all items when excludedItems is empty array', () => {
const items: EntityInfoItemV1[] = [
{ name: 'Type', value: 'Table', visible: ['explore'] },
{ name: 'Owners', value: 'John Doe', visible: ['explore'] },
];
render(
<CommonEntitySummaryInfoV1
componentType="explore"
entityInfo={items}
excludedItems={[]}
/>
);
expect(screen.getByTestId('Type-label')).toBeInTheDocument();
expect(screen.getByTestId('Owners-label')).toBeInTheDocument();
});
it('renders all items when excludedItems is not provided', () => {
const items: EntityInfoItemV1[] = [
{ name: 'Type', value: 'Table', visible: ['explore'] },
{ name: 'Owners', value: 'John Doe', visible: ['explore'] },
];
render(
<CommonEntitySummaryInfoV1 componentType="explore" entityInfo={items} />
);
expect(screen.getByTestId('Type-label')).toBeInTheDocument();
expect(screen.getByTestId('Owners-label')).toBeInTheDocument();
});
it('combines excludedItems with visibility filtering', () => {
const items: EntityInfoItemV1[] = [
{ name: 'Type', value: 'Table', visible: ['explore'] },
{ name: 'Owners', value: 'John Doe', visible: ['explore'] },
{ name: 'Hidden', value: 'Secret', visible: ['other'] },
{ name: 'Tier', value: 'Gold', visible: ['explore'] },
];
render(
<CommonEntitySummaryInfoV1
componentType="explore"
entityInfo={items}
excludedItems={['Owners', 'Tier']}
/>
);
expect(screen.getByTestId('Type-label')).toBeInTheDocument();
expect(screen.queryByTestId('Owners-label')).toBeNull();
expect(screen.queryByTestId('Hidden-label')).toBeNull();
expect(screen.queryByTestId('Tier-label')).toBeNull();
});
it('shows domain item when isDomainVisible is true even if in excludedItems', () => {
const items: EntityInfoItemV1[] = [
{ name: 'Type', value: 'Table', visible: ['explore'] },
{ name: 'label.domain-plural', value: 'Domain A', visible: ['other'] },
];
render(
<CommonEntitySummaryInfoV1
isDomainVisible
componentType="explore"
entityInfo={items}
excludedItems={['label.domain-plural']}
/>
);
expect(screen.getByTestId('Type-label')).toBeInTheDocument();
expect(screen.queryByTestId('label.domain-plural-label')).toBeNull();
});
});

View File

@ -13,6 +13,7 @@
import Icon from '@ant-design/icons/lib/components/Icon';
import classNames from 'classnames';
import { isNil } from 'lodash';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { ReactComponent as IconExternalLink } from '../../../assets/svg/external-links.svg';
@ -27,14 +28,23 @@ const CommonEntitySummaryInfoV1: React.FC<CommonEntitySummaryInfoV1Props> = ({
entityInfo,
componentType,
isDomainVisible = false,
excludedItems = [],
}) => {
const { t } = useTranslation();
const isItemVisible = (item: EntityInfoItemV1) => {
const isDomain = isDomainVisible && item.name === t('label.domain-plural');
const visibleEntityInfo = useMemo(() => {
return entityInfo.filter((info) => {
if (excludedItems.includes(info.name)) {
return false;
}
return (item.visible || []).includes(componentType) || isDomain;
};
const isDomain =
isDomainVisible && info.name === t('label.domain-plural');
const isVisibleInComponent = (info.visible ?? []).includes(componentType);
return isVisibleInComponent || isDomain;
});
}, [entityInfo, componentType, isDomainVisible, excludedItems]);
const renderInfoValue = (info: EntityInfoItemV1) => {
if (!info.isLink) {
@ -64,7 +74,7 @@ const CommonEntitySummaryInfoV1: React.FC<CommonEntitySummaryInfoV1Props> = ({
return (
<div className="overview-section">
{entityInfo.filter(isItemVisible).map((info) => (
{visibleEntityInfo.map((info) => (
<div className="overview-row" key={info.name}>
<span
className={classNames('overview-label')}

View File

@ -37,6 +37,42 @@ jest.mock('../SectionWithEdit/SectionWithEdit', () => {
));
});
// Mock CommonEntitySummaryInfoV1 component
jest.mock('./CommonEntitySummaryInfoV1', () => {
return jest
.fn()
.mockImplementation(({ entityInfo, excludedItems = [], componentType }) => {
const filteredInfo = entityInfo.filter(
(item: { name: string; visible?: string[] }) => {
if (excludedItems.includes(item.name)) {
return false;
}
return !componentType || (item.visible ?? []).includes(componentType);
}
);
return (
<div className="overview-section">
{filteredInfo.map((info: { name: string; value: unknown }) => (
<div className="overview-row" key={info.name}>
<span
className="overview-label"
data-testid={`${info.name}-label`}>
{info.name}
</span>
<span
className="overview-value text-grey-body"
data-testid={`${info.name}-value`}>
{String(info.value)}
</span>
</div>
))}
</div>
);
});
});
const entityInfoV1 = [
{ name: 'Type', value: 'Table', visible: ['explore'] },
{ name: 'Rows', value: 1000, visible: ['explore'] },
@ -116,12 +152,25 @@ describe('OverviewSection', () => {
expect(values).toHaveLength(5);
});
it('should hide when nothing visible for given componentType', () => {
it('should exclude Owners and Tier items via excludedItems prop', () => {
const infoWithExcluded = [
{ name: 'Type', value: 'Table', visible: ['explore'] },
{ name: 'Owners', value: 'John Doe', visible: ['explore'] },
{ name: 'Tier', value: 'Gold', visible: ['explore'] },
{ name: 'Rows', value: 1000, visible: ['explore'] },
];
render(
<OverviewSection componentType="other" entityInfoV1={entityInfoV1} />
<OverviewSection
componentType="explore"
entityInfoV1={infoWithExcluded}
/>
);
expect(screen.queryByTestId('section-with-edit')).toBeNull();
expect(screen.getByTestId('Type-label')).toBeInTheDocument();
expect(screen.getByTestId('Rows-label')).toBeInTheDocument();
expect(screen.queryByTestId('Owners-label')).not.toBeInTheDocument();
expect(screen.queryByTestId('Tier-label')).not.toBeInTheDocument();
});
it('should handle single item', () => {

View File

@ -16,6 +16,8 @@ import CommonEntitySummaryInfoV1 from './CommonEntitySummaryInfoV1';
import { OverviewSectionProps } from './OverviewSection.interface';
import './OverviewSection.less';
const EXCLUDED_ITEMS = ['Owners', 'Tier'];
const OverviewSection: React.FC<OverviewSectionProps> = ({
onEdit,
showEditButton = false,
@ -25,20 +27,7 @@ const OverviewSection: React.FC<OverviewSectionProps> = ({
}) => {
const { t } = useTranslation();
// Compute visible rows when using entityInfoV1
const visibleEntityInfo = entityInfoV1
? entityInfoV1.filter((info) => {
const isDomain =
isDomainVisible && info.name === t('label.domain-plural');
return (info.visible || []).includes(componentType) || isDomain;
})
: [];
// Hide the entire section (including title) when there's no content
const hasContent = entityInfoV1 && visibleEntityInfo.length > 0;
if (!hasContent) {
if (!entityInfoV1 || entityInfoV1.length === 0) {
return null;
}
@ -47,13 +36,12 @@ const OverviewSection: React.FC<OverviewSectionProps> = ({
showEditButton={showEditButton}
title={t('label.overview')}
onEdit={onEdit}>
{entityInfoV1 && (
<CommonEntitySummaryInfoV1
componentType={componentType}
entityInfo={entityInfoV1}
isDomainVisible={isDomainVisible}
/>
)}
<CommonEntitySummaryInfoV1
componentType={componentType}
entityInfo={entityInfoV1}
excludedItems={EXCLUDED_ITEMS}
isDomainVisible={isDomainVisible}
/>
</SectionWithEdit>
);
};