fix: activity feed card header alignment (#14580)

* fix feed-card header alignment issue by revert back the using 2 UserPopOverCard code and fix some css

* fix role button on span tag issue

* replace span and onClick with the Link

* make the whole feed header text as paragraph

* fix: Header in ActivityFeedCard component

* fix a style issue

* update unit test of feed-card-header

---------

Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
Abhishek Porwal 2024-01-12 15:22:23 +05:30 committed by GitHub
parent 2307807bbb
commit 156dc29687
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 123 additions and 125 deletions

View File

@ -11,130 +11,108 @@
* limitations under the License.
*/
import {
findByTestId,
findByText,
queryByTestId,
render,
} from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { Thread, ThreadType } from '../../../../generated/entity/feed/thread';
import FeedCardHeader from './FeedCardHeader';
const FQN = 'service.database.schema.table';
const type = 'table';
const expectedDisplayName = 'database.schema.table';
jest.mock('../../../../rest/userAPI', () => ({
getUserByName: jest.fn().mockReturnValue({}),
}));
jest.mock('../../../../utils/CommonUtils', () => ({
getPartialNameFromFQN: jest.fn().mockReturnValue('feedcard'),
getNonDeletedTeams: jest.fn().mockReturnValue([]),
getEntityName: jest.fn().mockReturnValue('entityname'),
getPartialNameFromTableFQN: jest.fn().mockImplementation(() => {
return expectedDisplayName;
}),
getEntityDetailLink: jest.fn().mockImplementation(() => expectedDisplayName),
}));
jest.mock('../../../../utils/TableUtils', () => ({
getTierTags: jest.fn(),
getTagsWithoutTier: jest.fn(),
}));
jest.mock('../../../common/ProfilePicture/ProfilePicture', () => {
return jest.fn().mockReturnValue(<p>ProfilePicture</p>);
});
const mockFeedHeaderProps = {
createdBy: 'xyz',
entityFQN: 'x.y.v.z',
entityField: 'z',
entityType: 'y',
const mockProps1 = {
createdBy: 'username',
entityFQN: 'service.database.schema.table',
entityField: 'entityField',
entityType: 'table',
isEntityFeed: true,
timeStamp: 1647322547179,
feedType: ThreadType.Conversation,
task: {} as Thread,
};
const mockProps2 = {
...mockProps1,
isEntityFeed: false,
};
jest.mock('../../../../constants/constants', () => ({
getUserPath: jest.fn().mockReturnValue('user-profile-path'),
}));
jest.mock('../../../../hooks/user-profile/useUserProfile', () => ({
useUserProfile: jest.fn().mockReturnValue([]),
}));
jest.mock('../../../../utils/date-time/DateTimeUtils', () => ({
formatDateTime: jest.fn().mockImplementation((date) => date),
getRelativeTime: jest.fn().mockImplementation((date) => date),
}));
jest.mock('../../../../utils/EntityUtils', () => ({
getEntityName: jest.fn().mockReturnValue('username'),
}));
jest.mock('../../../../utils/FeedUtils', () => ({
entityDisplayName: jest.fn().mockReturnValue('database.schema.table'),
getEntityFieldDisplay: jest
.fn()
.mockImplementation((entityField) => entityField),
prepareFeedLink: jest.fn().mockReturnValue('entity-link'),
}));
jest.mock('../../../../utils/TasksUtils', () => ({
getTaskDetailPath: jest.fn().mockReturnValue('task detail path'),
}));
jest.mock('../../../common/PopOverCard/EntityPopOverCard', () =>
jest.fn().mockImplementation(({ children }) => (
<>
EntityPopOverCard
<div>{children}</div>
</>
))
);
jest.mock('../../../common/PopOverCard/UserPopOverCard', () =>
jest.fn().mockImplementation(({ children }) => (
<>
UserPopOverCard
<div>{children}</div>
</>
))
);
describe('Test FeedHeader Component', () => {
it('Checks if the FeedHeader component has isEntityFeed as true', async () => {
const { container } = render(<FeedCardHeader {...mockFeedHeaderProps} />, {
it('should contain all necessary elements, when isEntityFeed true', () => {
render(<FeedCardHeader {...mockProps1} />, {
wrapper: MemoryRouter,
});
const createdBy = await findByText(container, /xyz/i);
const headerElement = await findByTestId(container, 'headerText');
const entityFieldElement = await findByTestId(
container,
'headerText-entityField'
);
const entityTypeElement = queryByTestId(container, 'entityType');
const entityLinkElement = queryByTestId(container, 'entitylink');
const timeStampElement = await findByTestId(container, 'timestamp');
expect(screen.getByText('UserPopOverCard')).toBeInTheDocument();
expect(screen.getByText('username')).toBeInTheDocument();
expect(createdBy).toBeInTheDocument();
expect(screen.getByTestId('headerText-entityField')).toBeInTheDocument();
expect(headerElement).toBeInTheDocument();
expect(entityFieldElement).toBeInTheDocument();
expect(entityTypeElement).not.toBeInTheDocument();
expect(entityLinkElement).not.toBeInTheDocument();
expect(timeStampElement).toBeInTheDocument();
expect(timeStampElement).toHaveTextContent('1 year ago');
expect(screen.getByTestId('timestamp')).toBeInTheDocument();
expect(screen.queryByTestId('table')).not.toBeInTheDocument();
expect(screen.queryByTestId('entitylink')).not.toBeInTheDocument();
});
it('Checks if the FeedHeader component has isEntityFeed as false', async () => {
const { container } = render(
<FeedCardHeader {...mockFeedHeaderProps} isEntityFeed={false} />,
{
wrapper: MemoryRouter,
}
);
const createdBy = await findByText(container, /xyz/i);
it('should contain all necessary elements, when isEntityFeed false', () => {
render(<FeedCardHeader {...mockProps2} />, {
wrapper: MemoryRouter,
});
const headerElement = await findByTestId(container, 'headerText');
const entityFieldElement = queryByTestId(
container,
'headerText-entityField'
);
const entityTypeElement = await findByTestId(container, 'entityType');
const entityLinkElement = await findByTestId(container, 'entitylink');
const timeStampElement = await findByTestId(container, 'timestamp');
expect(screen.getByText('UserPopOverCard')).toBeInTheDocument();
expect(screen.getByText('username')).toBeInTheDocument();
expect(createdBy).toBeInTheDocument();
expect(screen.getByText('table')).toBeInTheDocument();
expect(screen.getByText('database.schema.table')).toBeInTheDocument();
expect(screen.getByTestId('entitylink')).toBeInTheDocument();
expect(headerElement).toBeInTheDocument();
expect(entityFieldElement).not.toBeInTheDocument();
expect(entityTypeElement).toBeInTheDocument();
expect(entityLinkElement).toBeInTheDocument();
expect(timeStampElement).toBeInTheDocument();
expect(timeStampElement).toHaveTextContent('1 year ago');
});
expect(screen.getByTestId('timestamp')).toBeInTheDocument();
it('Should show link text as `database.schema.table` if entity type is table', async () => {
const { container } = render(
<FeedCardHeader
{...mockFeedHeaderProps}
entityFQN={FQN}
entityType={type}
isEntityFeed={false}
/>,
{
wrapper: MemoryRouter,
}
);
const entityTypeElement = await findByTestId(container, 'entityType');
const entityLinkElement = await findByTestId(container, 'entitylink');
expect(entityTypeElement).toBeInTheDocument();
expect(entityLinkElement).toBeInTheDocument();
expect(entityTypeElement).toHaveTextContent(type);
expect(entityLinkElement).toHaveTextContent(expectedDisplayName);
expect(
screen.queryByTestId('headerText-entityField')
).not.toBeInTheDocument();
});
});

View File

@ -17,11 +17,14 @@ import { isUndefined } from 'lodash';
import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import { getUserPath } from '../../../../constants/constants';
import { ThreadType } from '../../../../generated/entity/feed/thread';
import { useUserProfile } from '../../../../hooks/user-profile/useUserProfile';
import {
formatDateTime,
getRelativeTime,
} from '../../../../utils/date-time/DateTimeUtils';
import { getEntityName } from '../../../../utils/EntityUtils';
import {
entityDisplayName,
getEntityFieldDisplay,
@ -31,6 +34,7 @@ import { getTaskDetailPath } from '../../../../utils/TasksUtils';
import EntityPopOverCard from '../../../common/PopOverCard/EntityPopOverCard';
import UserPopOverCard from '../../../common/PopOverCard/UserPopOverCard';
import { FeedHeaderProp } from '../ActivityFeedCard.interface';
import './feed-card-header-v1.style.less';
const FeedCardHeader: FC<FeedHeaderProp> = ({
className,
@ -43,6 +47,11 @@ const FeedCardHeader: FC<FeedHeaderProp> = ({
feedType,
task,
}) => {
const [, , user] = useUserProfile({
permission: true,
name: createdBy ?? '',
});
const { t } = useTranslation();
const { task: taskDetails } = task;
@ -119,8 +128,12 @@ const FeedCardHeader: FC<FeedHeaderProp> = ({
);
return (
<div className={classNames('d-inline-block', className)}>
<UserPopOverCard userName={createdBy}>{createdBy}</UserPopOverCard>
<div className={classNames('d-inline-block feed-header', className)}>
<UserPopOverCard userName={createdBy}>
<Link className="thread-author m-r-xss" to={getUserPath(createdBy)}>
{getEntityName(user)}
</Link>
</UserPopOverCard>
{feedType === ThreadType.Conversation && getFeedLinkElement}
{feedType === ThreadType.Task && getTaskLinkElement}

View File

@ -17,10 +17,13 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
import EntityPopOverCard from '../../../../components/common/PopOverCard/EntityPopOverCard';
import { getUserPath } from '../../../../constants/constants';
import { useUserProfile } from '../../../../hooks/user-profile/useUserProfile';
import {
formatDateTime,
getRelativeTime,
} from '../../../../utils/date-time/DateTimeUtils';
import { getEntityName } from '../../../../utils/EntityUtils';
import {
entityDisplayName,
getEntityField,
@ -49,6 +52,11 @@ const FeedCardHeaderV1 = ({
className = '',
isEntityFeed = false,
}: FeedCardHeaderV1Props) => {
const [, , user] = useUserProfile({
permission: true,
name: createdBy ?? '',
});
const { t } = useTranslation();
const entityType = getEntityType(entityLink) ?? '';
@ -59,7 +67,7 @@ const FeedCardHeaderV1 = ({
const getFeedLinkElement = entityCheck && (
<span className="font-normal" data-testid="headerText">
<span className="m-x-xss">{t('label.posted-on-lowercase')}</span>
<span className="m-r-xss">{t('label.posted-on-lowercase')}</span>
{isEntityFeed ? (
<span data-testid="headerText-entityField">
{getEntityFieldDisplay(entityField)}
@ -96,20 +104,25 @@ const FeedCardHeaderV1 = ({
return (
<div className={classNames('feed-header', className)}>
<UserPopOverCard
showUserName
className="thread-author"
userName={createdBy}
/>
{getFeedLinkElement}
<UserPopOverCard userName={createdBy} />
{timeStamp && (
<Tooltip title={formatDateTime(timeStamp)}>
<span className="feed-header-timestamp" data-testid="timestamp">
{getRelativeTime(timeStamp)}
</span>
</Tooltip>
)}
<p className="feed-header-content break-word">
<UserPopOverCard userName={createdBy}>
<Link className="thread-author m-r-xss" to={getUserPath(createdBy)}>
{getEntityName(user)}
</Link>
</UserPopOverCard>
{getFeedLinkElement}
{timeStamp && (
<Tooltip title={formatDateTime(timeStamp)}>
<span className="feed-header-timestamp" data-testid="timestamp">
{getRelativeTime(timeStamp)}
</span>
</Tooltip>
)}
</p>
</div>
);
};

View File

@ -15,7 +15,7 @@
.feed-header {
display: flex;
align-items: center;
align-items: flex-start;
.thread-author {
font-weight: 600;
@ -31,9 +31,6 @@
}
.feed-header-content {
line-height: 20px;
display: inline-flex;
align-items: center;
flex-wrap: wrap;
}
}

View File

@ -20,8 +20,5 @@
overflow-y: scroll;
overflow-x: hidden;
max-height: 90%;
.feed-card-body {
padding: 0;
}
}
}