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:
Anujkumar Yadav 2025-09-17 06:02:14 +05:30 committed by GitHub
parent 73eb1fd0d9
commit ba61af7465
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 400 additions and 97 deletions

View File

@ -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();
});

View File

@ -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(

View File

@ -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 (

View File

@ -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 (

View File

@ -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>

View File

@ -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({

View File

@ -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;

View File

@ -33,6 +33,7 @@ import { TagsV1Props } from './TagsV1.interface';
import './tagsV1.less';
const TagsV1 = ({
hideIcon,
tag,
startWith,
className,
@ -131,34 +132,43 @@ const TagsV1 = ({
return '';
}, [newLook, tag.style?.color]);
const renderIcon = useMemo(() => {
if (hideIcon) {
return null;
}
if (tag.style?.iconURL) {
return (
<img
className="m-r-xss"
data-testid="icon"
height={12}
src={tag.style.iconURL}
width={12}
/>
);
}
return startIcon;
}, [hideIcon, tag.style?.iconURL, startIcon]);
const tagContent = useMemo(
() => (
<div className="d-flex w-full h-full">
<div className="d-flex w-full">
{tagColorBar}
<div
className={classNames(
'd-flex items-center p-x-xs w-full',
'd-flex items-center p-x-xs w-full tag-content-container',
tagChipStyleClass
)}>
{tag.style?.iconURL ? (
<img
className="m-r-xss"
data-testid="icon"
height={12}
src={tag.style.iconURL}
width={12}
/>
) : (
startIcon
)}
<Typography.Paragraph
ellipsis
className="m-0 tags-label"
{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]

View File

@ -21,6 +21,7 @@ export interface DataTestId {
}
export type TagsV1Props = {
hideIcon?: boolean;
tag: TagLabel | HighlightedTagLabel;
startWith: TAG_START_WITH;
showOnlyName?: boolean;

View File

@ -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;
}

View File

@ -57,6 +57,7 @@ const CertificationTag = ({
backgroundColor: certification.tagLabel.style?.color
? certification.tagLabel.style?.color + '33'
: '#f8f8f8',
padding: '2px 6px',
}
: {};

View File

@ -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 "

View File

@ -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;

View File

@ -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();
});
});

View File

@ -13,6 +13,7 @@
import { EntityReference } from '../../../generated/tests/testCase';
export interface NoOwnerFoundProps {
showDashPlaceholder?: boolean;
isCompactView: boolean;
placeHolder?: string;
showLabel?: boolean;

View File

@ -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>

View File

@ -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,18 +263,16 @@ export const OwnerLabel = ({
</div>
{showMoreButton && !isCompactView && (
<div className="m-l-xs">
<OwnerReveal
avatarSize={isCompactView ? 24 : avatarSize}
isCompactView={isCompactView}
isDropdownOpen={isDropdownOpen}
owners={owners.slice(maxVisibleOwners)}
remainingCount={remainingOwnersCount}
setIsDropdownOpen={setIsDropdownOpen}
setShowAllOwners={setShowAllOwners}
showAllOwners={showAllOwners}
/>
</div>
<OwnerReveal
avatarSize={isCompactView ? 24 : avatarSize}
isCompactView={isCompactView}
isDropdownOpen={isDropdownOpen}
owners={owners.slice(maxVisibleOwners)}
remainingCount={remainingOwnersCount}
setIsDropdownOpen={setIsDropdownOpen}
setShowAllOwners={setShowAllOwners}
showAllOwners={showAllOwners}
/>
)}
</div>
{isCompactView && onUpdate && (

View File

@ -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;

View File

@ -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>