mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-26 15:46:17 +00:00
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:
parent
e18c848851
commit
7745cc1330
@ -26,4 +26,5 @@ export interface CommonEntitySummaryInfoV1Props {
|
||||
entityInfo: EntityInfoItemV1[];
|
||||
componentType: string;
|
||||
isDomainVisible?: boolean;
|
||||
excludedItems?: string[];
|
||||
}
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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', () => {
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user