Fix : User Profile edit widgets and Entity Popover Card (#20535)

* fix user profile edit widget chips

* minor fix for inherited domains tag

* fix entity popover card style issue
This commit is contained in:
Shrushti Polekar 2025-04-02 11:09:16 +05:30 committed by GitHub
parent 3ea4dbd10e
commit 9d19bd3eca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 203 additions and 43 deletions

View File

@ -28,6 +28,7 @@ import {
getEntityName,
getEntityReferenceListFromEntities,
} from '../../../../utils/EntityUtils';
import { TagRenderer } from '../../../common/TagRenderer/TagRenderer';
import { PersonaSelectableListProps } from './PersonaSelectableList.interface';
export const PersonaListItemRenderer = (props: EntityReference) => {
@ -210,6 +211,7 @@ export const PersonaSelectableList = ({
popupClassName="persona-custom-dropdown-class"
ref={dropdownRef as any}
style={{ width: '100%' }}
tagRender={TagRenderer}
onChange={(selectedIds) => {
const selectedPersonasList = selectOptions.filter((persona) =>
selectedIds.includes(persona.id)

View File

@ -21,6 +21,7 @@ import { TeamHierarchy } from '../../../../generated/entity/teams/teamHierarchy'
import { getTeamsHierarchy } from '../../../../rest/teamsAPI';
import { getEntityName } from '../../../../utils/EntityUtils';
import { showErrorToast } from '../../../../utils/ToastUtils';
import { TagRenderer } from '../../../common/TagRenderer/TagRenderer';
import { TeamsSelectableProps } from './TeamsSelectable.interface';
const TeamsSelectableNew = forwardRef<any, TeamsSelectableProps>(
@ -124,6 +125,7 @@ const TeamsSelectableNew = forwardRef<any, TeamsSelectableProps>(
ref={ref as any}
showCheckedStrategy={TreeSelect.SHOW_CHILD}
style={{ width: '100%' }}
tagRender={TagRenderer}
treeData={teamsTree}
treeLine={{ showLeafIcon }}
treeNodeFilterProp="title"

View File

@ -231,7 +231,7 @@ const UserProfileRoles = ({
dropdownMatchSelectWidth={false}
filterOption={handleSearchFilterOption}
loading={isLoading}
maxTagCount={4}
maxTagCount={3}
maxTagPlaceholder={(omittedValues) => (
<span className="max-tag-text">
{t('label.plus-count-more', {

View File

@ -151,7 +151,7 @@ const UserProfileTeams = ({
<TeamsSelectableNew
filterJoinable
handleDropdownChange={handleDropdownChange}
maxValueCount={4}
maxValueCount={3}
ref={teamsSelectableRef}
selectedTeams={selectedTeams}
onSelectionChange={setSelectedTeams}

View File

@ -332,7 +332,7 @@
.ant-select-item-option-selected,
.ant-select-selection-item {
font-size: 14px;
font-size: 12px;
}
.ant-select:not(.ant-select-customize-input) {
@ -468,3 +468,36 @@
vertical-align: text-top;
}
}
.selected-chip-tag-remove {
margin-left: 4px;
font-size: 12px;
cursor: pointer;
padding: 0 4px;
border: none;
background: none;
svg {
width: 10px;
height: 10px;
color: @grey-4;
}
}
.domain-link {
max-width: 180px;
display: inline-block;
.domain-link-name {
color: @text-color;
font-weight: 400;
&:hover {
color: @blue-9;
}
}
}
.text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 140px;
}

View File

@ -51,7 +51,9 @@ const Chip = ({
item.fullyQualifiedName ?? ''
)}>
{icon}
<Typography.Text className="text-left chip-tag-link">
<Typography.Text
className="text-left chip-tag-link chip-name"
ellipsis={{ tooltip: getEntityName(item) }}>
{getEntityName(item)}
</Typography.Text>
</Link>
@ -73,7 +75,7 @@ const Chip = ({
return (
<Row
wrap
className="align-middle d-flex flex-col flex-start justify-center"
className="align-middle d-flex flex-col flex-start justify-center chip-container"
data-testid="chip-container"
gutter={[20, 0]}>
{(isExpanded ? data : data.slice(0, USER_DATA_SIZE)).map(getChipElement)}

View File

@ -11,26 +11,31 @@
* limitations under the License.
*/
@import (reference) url('../../../styles/variables.less');
.chip-container {
width: 180px;
.chip-tag-link {
font-weight: 400;
font-size: 14px;
line-height: 20px;
color: @text-color;
&:hover {
color: @blue-9;
text-decoration: none;
.chip-tag-link {
font-weight: 400;
font-size: 14px;
line-height: 20px;
color: @text-color;
&:hover {
color: @blue-9;
text-decoration: none;
}
overflow: hidden;
text-overflow: ellipsis;
}
}
.chip-text {
color: @blue-9;
&:hover {
.chip-text {
color: @blue-9;
}
&::after {
animation: none !important;
display: none !important;
&:hover {
color: @blue-9;
}
&::after {
animation: none !important;
display: none !important;
}
}
}
.no-data-chip-placeholder {

View File

@ -134,7 +134,8 @@ export const DomainLabelNew = ({
domain,
domainDisplayName,
showDomainHeading,
'chip-tag-link'
'chip-tag-link',
true
)}
{inheritedIcon && (
<div className="d-flex">{inheritedIcon}</div>

View File

@ -91,7 +91,7 @@ const DomainSelectableListNew = ({
}
};
const [popoverHeight, setPopoverHeight] = useState<number>(136);
const [popoverHeight, setPopoverHeight] = useState<number>(156);
const dropdownRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {

View File

@ -37,6 +37,7 @@ import { getEntityReferenceFromEntity } from '../../../utils/EntityUtils';
import { findItemByFqn } from '../../../utils/GlossaryUtils';
import { showErrorToast } from '../../../utils/ToastUtils';
import Loader from '../Loader/Loader';
import { TagRenderer } from '../TagRenderer/TagRenderer';
import './domain-selectable.less';
import {
DomainSelectableTreeProps,
@ -227,7 +228,7 @@ const DomainSelectablTreeNew: FC<DomainSelectableTreeProps> = ({
className="custom-domain-edit-select"
dropdownRender={() => treeContent}
dropdownStyle={{ maxHeight: '200px' }}
maxTagCount={2}
maxTagCount={3}
maxTagPlaceholder={(omittedValues) => (
<span className="max-tag-text">
{t('label.plus-count-more', { count: omittedValues.length })}
@ -241,7 +242,7 @@ const DomainSelectablTreeNew: FC<DomainSelectableTreeProps> = ({
placeholder="Select a domain"
popupClassName="domain-custom-dropdown-class"
ref={dropdownRef as any}
style={{}}
tagRender={TagRenderer}
value={
selectedDomains
?.map((domain) => domain.fullyQualifiedName)

View File

@ -262,7 +262,7 @@ const EntityPopOverCard: FC<Props> = ({
}) => {
return (
<Popover
align={{ targetOffset: [0, -10] }}
align={{ targetOffset: [0, 10] }}
content={
<PopoverContent
entityFQN={entityFQN}

View File

@ -25,4 +25,8 @@
top: 0;
right: 0;
}
&.ant-popover-placement-top {
padding-top: 0px;
padding-bottom: 0px;
}
}

View File

@ -0,0 +1,60 @@
/*
* 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 { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { TagRenderer } from './TagRenderer';
describe('TagRenderer', () => {
const mockOnClose = jest.fn();
const defaultProps = {
label: 'Test Label',
value: 'test-value',
closable: true,
onClose: mockOnClose,
disabled: false,
onMouseDown: jest.fn(),
};
beforeEach(() => {
jest.clearAllMocks();
});
it('should render the label', () => {
render(<TagRenderer {...defaultProps} />);
expect(screen.getByText('Test Label')).toBeInTheDocument();
});
it('should truncate long labels and add ellipsis', () => {
const longLabel = 'test persona test test persona 5';
render(<TagRenderer {...defaultProps} label={longLabel} />);
expect(screen.getByText('test persona...')).toBeInTheDocument();
expect(screen.getByTitle(longLabel)).toBeInTheDocument();
});
it('should render close button when closable is true', () => {
render(<TagRenderer {...defaultProps} />);
expect(screen.getByRole('button')).toBeInTheDocument();
});
it('should call onClose when close button is clicked', () => {
render(<TagRenderer {...defaultProps} />);
const closeButton = screen.getByRole('button');
fireEvent.click(closeButton);
expect(mockOnClose).toHaveBeenCalledTimes(1);
});
});

View File

@ -0,0 +1,34 @@
/*
* 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 { CloseOutlined } from '@ant-design/icons';
import type { CustomTagProps } from 'rc-select/lib/BaseSelect';
import React from 'react';
export const TagRenderer = (props: CustomTagProps) => {
const { label, closable, onClose } = props;
const displayLabel =
typeof label === 'string' && label.length > 12
? `${label.substring(0, 12)}...`
: label;
return (
<span className="ant-select-selection-item" title={(label as string) ?? ''}>
{displayLabel}
{closable && (
<button className="selected-chip-tag-remove" onClick={onClose}>
<CloseOutlined />
</button>
)}
</span>
);
};

View File

@ -229,23 +229,39 @@ export const renderDomainLink = (
domain: EntityReference,
domainDisplayName: ReactNode,
showDomainHeading: boolean,
textClassName?: string
) => (
<Tooltip title={domainDisplayName ?? getEntityName(domain)}>
<Link
className={classNames(
'no-underline domain-link domain-link-text font-medium',
{ 'text-sm': !showDomainHeading },
textClassName
)}
data-testid="domain-link"
to={getDomainPath(domain?.fullyQualifiedName)}>
{isUndefined(domainDisplayName)
? getEntityName(domain)
: domainDisplayName}
</Link>
</Tooltip>
);
textClassName?: string,
trimLink?: boolean
) => {
const displayName = isUndefined(domainDisplayName)
? getEntityName(domain)
: domainDisplayName;
return (
<Tooltip title={domainDisplayName ?? getEntityName(domain)}>
<Link
className={classNames(
'no-underline domain-link domain-link-text font-medium',
{
'text-sm': !showDomainHeading,
'text-truncate': trimLink,
},
textClassName
)}
data-testid="domain-link"
to={getDomainPath(domain?.fullyQualifiedName)}>
{trimLink ? (
<Typography.Text
className="domain-link-name"
ellipsis={{ tooltip: false }}>
{displayName}
</Typography.Text>
) : (
<>{displayName}</>
)}
</Link>
</Tooltip>
);
};
export const initializeDomainEntityRef = (
domains: EntityReference[],