mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-30 03:16:30 +00:00
Revamp: improve data asset header layout and component styling (#23331)
* Revamp: improve data asset header layout and component styling * Remove style * fix minor styling * Fix integration test * fix no data placeholder * Addressed PR comments * fix integration test * Fix e2e test * Addressed comments * Fix domain count overflow
This commit is contained in:
parent
73eb1fd0d9
commit
ba61af7465
@ -1024,13 +1024,11 @@ test.describe('Bulk Import Export', () => {
|
||||
page.getByTestId('glossary-container').getByTestId('add-tag')
|
||||
).toBeVisible();
|
||||
|
||||
await expect(page.getByTestId('Tier')).toContainText('No Tier');
|
||||
await expect(page.getByTestId('Tier')).toContainText('--');
|
||||
|
||||
await expect(page.getByTestId('certification-label')).toContainText(
|
||||
'No Certification'
|
||||
);
|
||||
await expect(page.getByTestId('certification-label')).toContainText('--');
|
||||
|
||||
await expect(page.getByTestId('owner-label')).toContainText('No Owners');
|
||||
await expect(page.getByTestId('owner-label')).toContainText('--');
|
||||
|
||||
await expect(page.getByTestId('no-domain-text')).toBeVisible();
|
||||
});
|
||||
|
@ -107,7 +107,7 @@ test('Logical TestSuite', async ({ page }) => {
|
||||
await assignDomain(page, domain1.responseData);
|
||||
// TODO: Add domain update
|
||||
// await updateDomain(page, domain2.responseData);
|
||||
await removeDomain(page, domain1.responseData);
|
||||
await removeDomain(page, domain1.responseData, false);
|
||||
});
|
||||
|
||||
await test.step(
|
||||
|
@ -262,7 +262,8 @@ export const updateDomain = async (
|
||||
|
||||
export const removeDomain = async (
|
||||
page: Page,
|
||||
domain: { name: string; displayName: string; fullyQualifiedName?: string }
|
||||
domain: { name: string; displayName: string; fullyQualifiedName?: string },
|
||||
showDashPlaceholder = true
|
||||
) => {
|
||||
await page.getByTestId('add-domain').click();
|
||||
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
|
||||
@ -280,7 +281,9 @@ export const removeDomain = async (
|
||||
await patchReq;
|
||||
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
|
||||
|
||||
await expect(page.getByTestId('no-domain-text')).toContainText('No Domains');
|
||||
await expect(page.getByTestId('no-domain-text')).toContainText(
|
||||
showDashPlaceholder ? '--' : 'No Domains'
|
||||
);
|
||||
};
|
||||
|
||||
export const assignDataProduct = async (
|
||||
|
@ -478,7 +478,7 @@ export const removeTier = async (page: Page, endpoint: string) => {
|
||||
await patchRequest;
|
||||
await clickOutside(page);
|
||||
|
||||
await expect(page.getByTestId('Tier')).toContainText('No Tier');
|
||||
await expect(page.getByTestId('Tier')).toContainText('--');
|
||||
};
|
||||
|
||||
export const assignCertification = async (
|
||||
@ -509,9 +509,7 @@ export const removeCertification = async (page: Page, endpoint: string) => {
|
||||
await patchRequest;
|
||||
await clickOutside(page);
|
||||
|
||||
await expect(page.getByTestId('certification-label')).toContainText(
|
||||
'No Certification'
|
||||
);
|
||||
await expect(page.getByTestId('certification-label')).toContainText('--');
|
||||
};
|
||||
|
||||
export const updateDescription = async (
|
||||
|
@ -32,6 +32,7 @@ import { OwnerLabel } from '../../../components/common/OwnerLabel/OwnerLabel.com
|
||||
import TierCard from '../../../components/common/TierCard/TierCard';
|
||||
import EntityHeaderTitle from '../../../components/Entity/EntityHeaderTitle/EntityHeaderTitle.component';
|
||||
import { AUTO_PILOT_APP_NAME } from '../../../constants/Applications.constant';
|
||||
import { NO_DATA_PLACEHOLDER } from '../../../constants/constants';
|
||||
import {
|
||||
EXCLUDE_AUTO_PILOT_SERVICE_TYPES,
|
||||
SERVICE_TYPES,
|
||||
@ -59,7 +60,6 @@ import { getDataQualityLineage } from '../../../rest/lineageAPI';
|
||||
import { getContainerByName } from '../../../rest/storageAPI';
|
||||
import {
|
||||
getDataAssetsHeaderInfo,
|
||||
getEntityExtraInfoLength,
|
||||
isDataAssetsWithServiceField,
|
||||
} from '../../../utils/DataAssetsHeader.utils';
|
||||
import { getDataContractStatusIcon } from '../../../utils/DataContract/DataContractUtils';
|
||||
@ -324,14 +324,6 @@ export const DataAssetsHeader = ({
|
||||
[entityType, dataAsset, entityName, parentContainers]
|
||||
);
|
||||
|
||||
const showCompressedExtraInfoItems = useMemo(
|
||||
() =>
|
||||
entityType === EntityType.METRIC
|
||||
? false
|
||||
: getEntityExtraInfoLength(extraInfo) <= 1,
|
||||
[extraInfo, entityType]
|
||||
);
|
||||
|
||||
const handleOpenTaskClick = () => {
|
||||
if (!dataAsset.fullyQualifiedName) {
|
||||
return;
|
||||
@ -663,15 +655,14 @@ export const DataAssetsHeader = ({
|
||||
|
||||
<Col span={24}>
|
||||
<div
|
||||
className={classNames('data-asset-header-metadata', {
|
||||
'data-asset-header-less-items': showCompressedExtraInfoItems,
|
||||
})}
|
||||
className="data-asset-header-metadata"
|
||||
data-testid="data-asset-header-metadata">
|
||||
{showDomain && (
|
||||
<>
|
||||
<DomainLabel
|
||||
headerLayout
|
||||
multiple
|
||||
showDashPlaceholder
|
||||
afterDomainUpdateAction={afterDomainUpdateAction}
|
||||
domains={(dataAsset as EntitiesWithDomainField).domains}
|
||||
entityFqn={dataAsset.fullyQualifiedName ?? ''}
|
||||
@ -687,6 +678,8 @@ export const DataAssetsHeader = ({
|
||||
</>
|
||||
)}
|
||||
<OwnerLabel
|
||||
showDashPlaceholder
|
||||
avatarSize={24}
|
||||
hasPermission={editOwnerPermission}
|
||||
isCompactView={false}
|
||||
maxVisibleOwners={4}
|
||||
@ -697,7 +690,7 @@ export const DataAssetsHeader = ({
|
||||
{tierSuggestionRender ?? (
|
||||
<TierCard currentTier={tier?.tagFQN} updateTier={onTierUpdate}>
|
||||
<Space
|
||||
className="d-flex tier-container align-start"
|
||||
className="d-flex align-start"
|
||||
data-testid="header-tier-container">
|
||||
{tier ? (
|
||||
<div className="d-flex flex-col gap-2">
|
||||
@ -719,6 +712,7 @@ export const DataAssetsHeader = ({
|
||||
</div>
|
||||
|
||||
<TagsV1
|
||||
hideIcon
|
||||
startWith={TAG_START_WITH.SOURCE_ICON}
|
||||
tag={tier}
|
||||
tagProps={{
|
||||
@ -746,9 +740,7 @@ export const DataAssetsHeader = ({
|
||||
<span
|
||||
className="font-medium no-tier-text text-sm"
|
||||
data-testid="Tier">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.tier'),
|
||||
})}
|
||||
{NO_DATA_PLACEHOLDER}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
@ -819,9 +811,7 @@ export const DataAssetsHeader = ({
|
||||
certification={(dataAsset as Table).certification!}
|
||||
/>
|
||||
) : (
|
||||
t('label.no-entity', {
|
||||
entity: t('label.certification'),
|
||||
})
|
||||
NO_DATA_PLACEHOLDER
|
||||
)}
|
||||
</div>
|
||||
</Typography.Text>
|
||||
|
@ -269,7 +269,7 @@ describe('DataAssetsHeader component', () => {
|
||||
it('should not render the Tier data if not present', () => {
|
||||
render(<DataAssetsHeader {...mockProps} dataAsset={mockProps.dataAsset} />);
|
||||
|
||||
expect(screen.getByTestId('Tier')).toContainHTML('label.no-entity');
|
||||
expect(screen.getByTestId('Tier')).toContainHTML('--');
|
||||
});
|
||||
|
||||
it('should not call getDataQualityLineage, if isDqAlertSupported and alert supported is false', () => {
|
||||
@ -389,9 +389,7 @@ describe('DataAssetsHeader component', () => {
|
||||
// Test without certification when serviceCategory is undefined
|
||||
render(<DataAssetsHeader {...mockProps} />);
|
||||
|
||||
expect(screen.getByTestId('certification-label')).toContainHTML(
|
||||
'label.no-entity'
|
||||
);
|
||||
expect(screen.getByTestId('certification-label')).toContainHTML('--');
|
||||
|
||||
// Reset the mock to original value
|
||||
useRequiredParamsMock.mockReturnValue({
|
||||
|
@ -20,18 +20,9 @@
|
||||
gap: 16px;
|
||||
display: flex;
|
||||
background-color: @background-color;
|
||||
justify-content: space-evenly;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.data-asset-header-less-items {
|
||||
justify-content: initial;
|
||||
|
||||
.ant-divider {
|
||||
margin: 0 20px 0 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-space-item {
|
||||
.entity-no-tier {
|
||||
color: @primary-heading-color;
|
||||
|
@ -33,6 +33,7 @@ import { TagsV1Props } from './TagsV1.interface';
|
||||
import './tagsV1.less';
|
||||
|
||||
const TagsV1 = ({
|
||||
hideIcon,
|
||||
tag,
|
||||
startWith,
|
||||
className,
|
||||
@ -131,16 +132,13 @@ const TagsV1 = ({
|
||||
return '';
|
||||
}, [newLook, tag.style?.color]);
|
||||
|
||||
const tagContent = useMemo(
|
||||
() => (
|
||||
<div className="d-flex w-full h-full">
|
||||
{tagColorBar}
|
||||
<div
|
||||
className={classNames(
|
||||
'd-flex items-center p-x-xs w-full',
|
||||
tagChipStyleClass
|
||||
)}>
|
||||
{tag.style?.iconURL ? (
|
||||
const renderIcon = useMemo(() => {
|
||||
if (hideIcon) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (tag.style?.iconURL) {
|
||||
return (
|
||||
<img
|
||||
className="m-r-xss"
|
||||
data-testid="icon"
|
||||
@ -148,17 +146,29 @@ const TagsV1 = ({
|
||||
src={tag.style.iconURL}
|
||||
width={12}
|
||||
/>
|
||||
) : (
|
||||
startIcon
|
||||
)}
|
||||
);
|
||||
}
|
||||
|
||||
<Typography.Paragraph
|
||||
ellipsis
|
||||
className="m-0 tags-label"
|
||||
return startIcon;
|
||||
}, [hideIcon, tag.style?.iconURL, startIcon]);
|
||||
|
||||
const tagContent = useMemo(
|
||||
() => (
|
||||
<div className="d-flex w-full">
|
||||
{tagColorBar}
|
||||
<div
|
||||
className={classNames(
|
||||
'd-flex items-center p-x-xs w-full tag-content-container',
|
||||
tagChipStyleClass
|
||||
)}>
|
||||
{renderIcon}
|
||||
<Typography.Text
|
||||
className="m-0 tags-label text-truncate truncate w-max-full"
|
||||
data-testid={`tag-${tag.tagFQN}`}
|
||||
ellipsis={{ tooltip: false }}
|
||||
style={{ color: tag.style?.color }}>
|
||||
{tagName}
|
||||
</Typography.Paragraph>
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
@ -205,11 +215,12 @@ const TagsV1 = ({
|
||||
<Tag
|
||||
className="tag-chip tag-chip-add-button"
|
||||
icon={<PlusIcon height={16} name="plus" width={16} />}>
|
||||
<Typography.Paragraph
|
||||
className="m-0 text-xs font-medium text-primary"
|
||||
data-testid="add-tag">
|
||||
<Typography.Text
|
||||
className="m-0 text-xs font-medium text-primary text-truncate truncate w-max-full"
|
||||
data-testid="add-tag"
|
||||
ellipsis={{ tooltip: false }}>
|
||||
{getTagDisplay(tagName)}
|
||||
</Typography.Paragraph>
|
||||
</Typography.Text>
|
||||
</Tag>
|
||||
),
|
||||
[tagName]
|
||||
|
@ -21,6 +21,7 @@ export interface DataTestId {
|
||||
}
|
||||
|
||||
export type TagsV1Props = {
|
||||
hideIcon?: boolean;
|
||||
tag: TagLabel | HighlightedTagLabel;
|
||||
startWith: TAG_START_WITH;
|
||||
showOnlyName?: boolean;
|
||||
|
@ -37,11 +37,12 @@
|
||||
|
||||
.ant-tag.tag-chip-content {
|
||||
width: max-content;
|
||||
height: 30px;
|
||||
max-width: 200px;
|
||||
margin: 0px;
|
||||
border: none;
|
||||
padding: 0;
|
||||
padding: 2px 0;
|
||||
background: @tag-background-color;
|
||||
border-radius: @border-rad-xs;
|
||||
|
||||
&.diff-added {
|
||||
background: @success-background-color;
|
||||
@ -90,8 +91,15 @@
|
||||
}
|
||||
|
||||
.tags-label {
|
||||
display: inline-block;
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: inherit;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tag-content-container {
|
||||
min-width: 0;
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ const CertificationTag = ({
|
||||
backgroundColor: certification.tagLabel.style?.color
|
||||
? certification.tagLabel.style?.color + '33'
|
||||
: '#f8f8f8',
|
||||
padding: '2px 6px',
|
||||
}
|
||||
: {};
|
||||
|
||||
|
@ -19,7 +19,10 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as DomainIcon } from '../../../assets/svg/ic-domain.svg';
|
||||
import { ReactComponent as InheritIcon } from '../../../assets/svg/ic-inherit.svg';
|
||||
import { DE_ACTIVE_COLOR } from '../../../constants/constants';
|
||||
import {
|
||||
DE_ACTIVE_COLOR,
|
||||
NO_DATA_PLACEHOLDER,
|
||||
} from '../../../constants/constants';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
import {
|
||||
getAPIfromSource,
|
||||
@ -34,6 +37,7 @@ import './domain-label.less';
|
||||
import { DomainLabelProps } from './DomainLabel.interface';
|
||||
|
||||
export const DomainLabel = ({
|
||||
showDashPlaceholder,
|
||||
afterDomainUpdateAction,
|
||||
hasPermission,
|
||||
domains,
|
||||
@ -50,6 +54,12 @@ export const DomainLabel = ({
|
||||
const { t } = useTranslation();
|
||||
const [activeDomain, setActiveDomain] = useState<EntityReference[]>([]);
|
||||
|
||||
const defaultDomainText = useMemo(() => {
|
||||
return showDashPlaceholder
|
||||
? NO_DATA_PLACEHOLDER
|
||||
: t('label.no-entity', { entity: t('label.domain-plural') });
|
||||
}, [showDashPlaceholder]);
|
||||
|
||||
const handleDomainSave = useCallback(
|
||||
async (selectedDomain: EntityReference | EntityReference[]) => {
|
||||
const entityDetails = getEntityAPIfromSource(entityType as AssetsUnion)(
|
||||
@ -166,8 +176,12 @@ export const DomainLabel = ({
|
||||
})),
|
||||
className: 'domain-tooltip-list',
|
||||
}}>
|
||||
<Typography.Text className="domain-count-button flex-center text-sm font-medium">
|
||||
<span>+{remainingCount}</span>
|
||||
<Typography.Text
|
||||
className={`flex-center cursor-pointer align-middle ant-typography-secondary domain-count-button ${
|
||||
remainingCount <= 9 ? 'h-6 w-6' : ''
|
||||
}`}
|
||||
data-testid="domain-count-button">
|
||||
<span className="ant-typography domain-count-label">{`+${remainingCount}`}</span>
|
||||
</Typography.Text>
|
||||
</Dropdown>
|
||||
</div>
|
||||
@ -185,7 +199,7 @@ export const DomainLabel = ({
|
||||
textClassName
|
||||
)}
|
||||
data-testid="no-domain-text">
|
||||
{t('label.no-entity', { entity: t('label.domain-plural') })}
|
||||
{defaultDomainText}
|
||||
</Typography.Text>
|
||||
);
|
||||
}, [
|
||||
@ -226,7 +240,7 @@ export const DomainLabel = ({
|
||||
<Typography.Text className="domain-link right-panel-label m-r-xss">
|
||||
{activeDomain.length > 0
|
||||
? t('label.domain-plural')
|
||||
: t('label.no-entity', { entity: t('label.domain-plural') })}
|
||||
: defaultDomainText}
|
||||
</Typography.Text>
|
||||
)}
|
||||
{selectableList}
|
||||
@ -240,7 +254,7 @@ export const DomainLabel = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="d-flex flex-col domain-label-container gap-2 justify-start">
|
||||
<div className="d-flex flex-col gap-2 justify-start">
|
||||
{headerLayout && (
|
||||
<div
|
||||
className="d-flex text-sm gap-1 font-medium items-center "
|
||||
|
@ -17,6 +17,7 @@ import { User } from '../../../generated/entity/teams/user';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
|
||||
export type DomainLabelProps = {
|
||||
showDashPlaceholder?: boolean;
|
||||
afterDomainUpdateAction?: (asset: DataAssetWithDomains) => void;
|
||||
hasPermission?: boolean;
|
||||
domains: EntityReference[] | undefined;
|
||||
|
@ -0,0 +1,273 @@
|
||||
/*
|
||||
* 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 '@testing-library/jest-dom';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
import { DomainLabel } from './DomainLabel.component';
|
||||
|
||||
jest.mock('../../../utils/EntityUtils', () => ({
|
||||
getEntityName: jest
|
||||
.fn()
|
||||
.mockImplementation((entity) => entity?.name || 'Unknown'),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/RouterUtils', () => ({
|
||||
getDomainPath: jest
|
||||
.fn()
|
||||
.mockImplementation((fqn: string) => `/domain/${fqn}`),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/Assets/AssetsUtils', () => ({
|
||||
getAPIfromSource: jest.fn().mockReturnValue(jest.fn()),
|
||||
getEntityAPifromSource: jest.fn().mockReturnValue(jest.fn()),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/DomainUtils', () => ({
|
||||
renderDomainLink: jest
|
||||
.fn()
|
||||
.mockImplementation((domain, displayName, className) => (
|
||||
<a
|
||||
className={`domain-link ${className || ''}`}
|
||||
data-testid="domain-link"
|
||||
href={`/domain/${domain.fullyQualifiedName}`}>
|
||||
{displayName || domain.name}
|
||||
</a>
|
||||
)),
|
||||
}));
|
||||
|
||||
jest.mock('../../../utils/ToastUtils', () => ({
|
||||
showErrorToast: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../assets/svg/ic-domain.svg', () => ({
|
||||
ReactComponent: () => <div data-testid="domain-icon">Domain Icon</div>,
|
||||
}));
|
||||
|
||||
jest.mock('../../../assets/svg/ic-inherit.svg', () => ({
|
||||
ReactComponent: () => <div data-testid="inherit-icon">Inherit Icon</div>,
|
||||
}));
|
||||
|
||||
jest.mock('../DomainSelectableList/DomainSelectableList.component', () => ({
|
||||
__esModule: true,
|
||||
default: ({ onUpdate, selectedDomain }: any) => (
|
||||
<button
|
||||
data-testid="domain-selectable-list"
|
||||
onClick={() => onUpdate && onUpdate(selectedDomain)}>
|
||||
Select Domain
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock data
|
||||
const mockDomain1: EntityReference = {
|
||||
id: 'domain-1',
|
||||
fullyQualifiedName: 'domain.one',
|
||||
name: 'Domain One',
|
||||
type: 'domain',
|
||||
};
|
||||
|
||||
const mockDomain2: EntityReference = {
|
||||
id: 'domain-2',
|
||||
fullyQualifiedName: 'domain.two',
|
||||
name: 'Domain Two',
|
||||
type: 'domain',
|
||||
};
|
||||
|
||||
const mockInheritedDomain: EntityReference = {
|
||||
id: 'domain-inherited',
|
||||
fullyQualifiedName: 'domain.inherited',
|
||||
name: 'Inherited Domain',
|
||||
type: 'domain',
|
||||
inherited: true,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
domains: [mockDomain1],
|
||||
entityType: EntityType.TABLE,
|
||||
entityFqn: 'test.table',
|
||||
entityId: 'test-id',
|
||||
};
|
||||
|
||||
const renderDomainLabel = (props: any = {}) =>
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<DomainLabel {...defaultProps} {...props} />
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
describe('DomainLabel Component', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render single domain correctly', () => {
|
||||
renderDomainLabel({ domains: [mockDomain1] });
|
||||
|
||||
expect(screen.getByTestId('domain-link')).toBeInTheDocument();
|
||||
expect(screen.getByText('Domain One')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render multiple domains with dropdown when multiple and headerLayout are true', () => {
|
||||
renderDomainLabel({
|
||||
domains: [mockDomain1, mockDomain2],
|
||||
multiple: true,
|
||||
headerLayout: true,
|
||||
});
|
||||
|
||||
expect(screen.getByText('Domain One')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('domain-count-button')).toBeInTheDocument();
|
||||
expect(screen.getByText('+1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render all domains when multiple is true but headerLayout is false', () => {
|
||||
renderDomainLabel({
|
||||
domains: [mockDomain1, mockDomain2],
|
||||
multiple: true,
|
||||
headerLayout: false,
|
||||
});
|
||||
|
||||
expect(screen.getByText('Domain One')).toBeInTheDocument();
|
||||
expect(screen.getByText('Domain Two')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('domain-count-button')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render "No Domains" text when domains array is empty', () => {
|
||||
renderDomainLabel({ domains: [] });
|
||||
|
||||
expect(screen.getByTestId('no-domain-text')).toBeInTheDocument();
|
||||
expect(screen.getByText('label.no-entity')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render "No Domains" text when domains is undefined', () => {
|
||||
renderDomainLabel({ domains: undefined });
|
||||
|
||||
expect(screen.getByTestId('no-domain-text')).toBeInTheDocument();
|
||||
expect(screen.getByText('label.no-entity')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render inherited domain with inherit icon', () => {
|
||||
renderDomainLabel({ domains: [mockInheritedDomain] });
|
||||
|
||||
expect(screen.getByText('Inherited Domain')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle domain with missing name', () => {
|
||||
const domainWithoutName = {
|
||||
...mockDomain1,
|
||||
name: undefined,
|
||||
};
|
||||
|
||||
renderDomainLabel({ domains: [domainWithoutName] });
|
||||
|
||||
expect(screen.getByTestId('domain-link')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render domain heading when showDomainHeading is true', () => {
|
||||
renderDomainLabel({ showDomainHeading: true });
|
||||
|
||||
expect(screen.getByTestId('header-domain-container')).toBeInTheDocument();
|
||||
expect(screen.getByText('label.domain-plural')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render domain heading when showDomainHeading is false', () => {
|
||||
renderDomainLabel({ showDomainHeading: false });
|
||||
|
||||
expect(screen.queryByText('Domains')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not show domain icon for single domain in header layout', () => {
|
||||
renderDomainLabel({
|
||||
headerLayout: true,
|
||||
multiple: false,
|
||||
domains: [mockDomain1],
|
||||
});
|
||||
|
||||
expect(screen.queryByTestId('domain-icon')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render DomainSelectableList when hasPermission is true', () => {
|
||||
renderDomainLabel({ hasPermission: true });
|
||||
|
||||
expect(screen.getByTestId('domain-selectable-list')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render DomainSelectableList when hasPermission is false', () => {
|
||||
renderDomainLabel({ hasPermission: false });
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('domain-selectable-list')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render DomainSelectableList when hasPermission is undefined', () => {
|
||||
renderDomainLabel({ hasPermission: undefined });
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('domain-selectable-list')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle domains as single object instead of array', () => {
|
||||
renderDomainLabel({ domains: mockDomain1 });
|
||||
|
||||
expect(screen.getByText('Domain One')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle empty domain object', () => {
|
||||
const emptyDomain = {} as EntityReference;
|
||||
|
||||
renderDomainLabel({ domains: [emptyDomain] });
|
||||
|
||||
expect(screen.getByTestId('domain-link')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle domain with empty fullyQualifiedName', () => {
|
||||
const domainWithEmptyFQN = {
|
||||
...mockDomain1,
|
||||
fullyQualifiedName: '',
|
||||
};
|
||||
|
||||
renderDomainLabel({ domains: [domainWithEmptyFQN] });
|
||||
|
||||
expect(screen.getByTestId('domain-link')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should handle mixed domain types (inherited and non-inherited)', () => {
|
||||
renderDomainLabel({
|
||||
domains: [mockDomain1, mockInheritedDomain],
|
||||
multiple: true,
|
||||
headerLayout: true,
|
||||
});
|
||||
|
||||
expect(screen.getByText('Domain One')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('domain-count-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have proper test ID for domain count button', () => {
|
||||
renderDomainLabel({
|
||||
domains: [mockDomain1, mockDomain2],
|
||||
multiple: true,
|
||||
headerLayout: true,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('domain-count-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should have proper test ID for no domain text', () => {
|
||||
renderDomainLabel({ domains: [] });
|
||||
|
||||
expect(screen.getByTestId('no-domain-text')).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -13,6 +13,7 @@
|
||||
import { EntityReference } from '../../../generated/tests/testCase';
|
||||
|
||||
export interface NoOwnerFoundProps {
|
||||
showDashPlaceholder?: boolean;
|
||||
isCompactView: boolean;
|
||||
placeHolder?: string;
|
||||
showLabel?: boolean;
|
||||
|
@ -13,13 +13,15 @@
|
||||
import Icon from '@ant-design/icons';
|
||||
import { Typography } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ReactComponent as IconUser } from '../../../assets/svg/user.svg';
|
||||
import { NO_DATA_PLACEHOLDER } from '../../../constants/constants';
|
||||
import { UserTeamSelectableList } from '../UserTeamSelectableList/UserTeamSelectableList.component';
|
||||
import { NoOwnerFoundProps } from './NoOwnerFound.interface';
|
||||
|
||||
export const NoOwnerFound: React.FC<NoOwnerFoundProps> = ({
|
||||
showDashPlaceholder,
|
||||
isCompactView,
|
||||
showLabel = true,
|
||||
placeHolder,
|
||||
@ -32,6 +34,22 @@ export const NoOwnerFound: React.FC<NoOwnerFoundProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const ownerPlaceholder = useMemo(() => {
|
||||
const defaultPlaceholder = showDashPlaceholder
|
||||
? NO_DATA_PLACEHOLDER
|
||||
: t('label.no-entity', { entity: t('label.owner-plural') });
|
||||
|
||||
if (placeHolder) {
|
||||
if (showLabel) {
|
||||
return defaultPlaceholder;
|
||||
}
|
||||
|
||||
return placeHolder;
|
||||
}
|
||||
|
||||
return defaultPlaceholder;
|
||||
}, [placeHolder, showLabel, showDashPlaceholder]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
@ -86,11 +104,7 @@ export const NoOwnerFound: React.FC<NoOwnerFoundProps> = ({
|
||||
|
||||
{!isCompactView && (
|
||||
<div className="no-owner-text text-sm font-medium">
|
||||
{placeHolder
|
||||
? showLabel
|
||||
? t('label.no-entity', { entity: placeHolder })
|
||||
: placeHolder
|
||||
: t('label.no-entity', { entity: t('label.owner-plural') })}
|
||||
{ownerPlaceholder}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@ -27,6 +27,7 @@ import './owner-label.less';
|
||||
import { OwnerLabelProps } from './OwnerLabel.interface';
|
||||
|
||||
export const OwnerLabel = ({
|
||||
showDashPlaceholder,
|
||||
owners = [],
|
||||
showLabel = true,
|
||||
className,
|
||||
@ -102,8 +103,8 @@ export const OwnerLabel = ({
|
||||
.slice(maxVisibleOwners);
|
||||
const renderMultipleType = useMemo(() => {
|
||||
return (
|
||||
<div className="flex-wrap w-max-full d-flex relative items-center">
|
||||
<div className="flex w-full gap-2 flex-wrap relative">
|
||||
<div className="w-max-full d-flex relative items-center">
|
||||
<div className="flex w-full gap-2 relative">
|
||||
{showMultipleTypeTeam.map((owner, index) => (
|
||||
<div className="w-max-full" key={owner.id}>
|
||||
<OwnerItem
|
||||
@ -197,6 +198,7 @@ export const OwnerLabel = ({
|
||||
multiple={multiple}
|
||||
owners={owners}
|
||||
placeHolder={placeHolder}
|
||||
showDashPlaceholder={showDashPlaceholder}
|
||||
showLabel={showLabel}
|
||||
tooltipText={tooltipText}
|
||||
onUpdate={onUpdate}
|
||||
@ -211,7 +213,7 @@ export const OwnerLabel = ({
|
||||
return (
|
||||
<div
|
||||
className={classNames({
|
||||
'owner-label-container w-full d-flex flex-col items-start flex-start':
|
||||
'owner-label-container d-flex flex-col items-start flex-start':
|
||||
!isCompactView,
|
||||
'd-flex owner-label-heading gap-2 items-center': isCompactView,
|
||||
})}
|
||||
@ -261,7 +263,6 @@ export const OwnerLabel = ({
|
||||
</div>
|
||||
|
||||
{showMoreButton && !isCompactView && (
|
||||
<div className="m-l-xs">
|
||||
<OwnerReveal
|
||||
avatarSize={isCompactView ? 24 : avatarSize}
|
||||
isCompactView={isCompactView}
|
||||
@ -272,7 +273,6 @@ export const OwnerLabel = ({
|
||||
setShowAllOwners={setShowAllOwners}
|
||||
showAllOwners={showAllOwners}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{isCompactView && onUpdate && (
|
||||
|
@ -14,6 +14,7 @@ import { ReactNode } from 'react';
|
||||
import { EntityReference } from '../../../generated/tests/testCase';
|
||||
|
||||
export interface OwnerLabelProps {
|
||||
showDashPlaceholder?: boolean;
|
||||
owners?: EntityReference[];
|
||||
showLabel?: boolean;
|
||||
className?: string;
|
||||
|
@ -104,7 +104,7 @@ export const ExtraInfoLabel = ({
|
||||
<span className="extra-info-label-heading">{label}</span>
|
||||
)}
|
||||
<div className={classNames('font-medium extra-info-value')}>
|
||||
{value}
|
||||
{value ?? NO_DATA_PLACEHOLDER}
|
||||
</div>
|
||||
</Typography.Text>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user