#16343: activity feed ui improvements (#16474)

* activity feed ui improvements

* fix mysql icon not showing in feed card

* fix comments not showing in right panel card

* remove unused code and added utils test

* supported playwright test for feed replies

* changes as per comments
This commit is contained in:
Ashish Gupta 2024-07-15 15:37:41 +05:30 committed by GitHub
parent 6e99fe7bda
commit e66aa5363b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 144 additions and 56 deletions

View File

@ -17,6 +17,7 @@ import {
createNewPage,
redirectToHomePage,
toastNotification,
visitUserProfilePage,
} from '../../utils/common';
import { clickOnLogo } from '../../utils/sidebar';
import {
@ -43,7 +44,6 @@ test.describe('Activity feed', () => {
test.beforeEach('Visit on landing page', async ({ page }) => {
await redirectToHomePage(page);
await entity.visitEntityPage(page);
});
test.afterAll('Cleanup', async ({ browser }) => {
@ -59,6 +59,7 @@ test.describe('Activity feed', () => {
term: entity.entity.name,
assignee: `${user.data.firstName}.${user.data.lastName}`,
};
await entity.visitEntityPage(page);
await page.getByTestId('request-description').click();
@ -126,4 +127,58 @@ test.describe('Activity feed', () => {
expect(closedTask).toContain('2 Closed');
});
test('User should be able to reply on feeds in ActivityFeed', async ({
page,
}) => {
await visitUserProfilePage(page);
const secondFeedConversation = page
.locator('#center-container [data-testid="message-container"]')
.nth(1);
await secondFeedConversation.locator('.feed-card-v2-sidebar').click();
await page.waitForSelector('#feed-panel', {
state: 'visible',
});
// Compare the text of the second feed in the center container with the right panel feed
const secondFeedText = await secondFeedConversation
.locator('[data-testid="headerText"]')
.innerText();
const rightPanelFeedText = await page
.locator(
'.right-container [data-testid="message-container"] [data-testid="headerText"]'
)
.innerText();
expect(secondFeedText).toBe(rightPanelFeedText);
for (let i = 1; i <= 3; i++) {
await page.fill(
'[data-testid="editor-wrapper"] .ql-editor',
`Reply message ${i}`
);
const sendReply = page.waitForResponse('/api/v1/feed/*/posts');
await page.getByTestId('send-button').click();
await sendReply;
}
// Compare after adding some feeds in the right panel
const rightPanelFeedTextCurrent = await page
.locator(
'.right-container [data-testid="message-container"] [data-testid="headerText"]'
)
.innerText();
expect(secondFeedText).toBe(rightPanelFeedTextCurrent);
for (let i = 1; i <= 3; i++) {
await expect(
page.locator('.right-container [data-testid="feed-replies"]')
).toContainText(`Reply message ${i}`);
}
});
});

View File

@ -147,3 +147,16 @@ export const clickOutside = async (page: Page) => {
}); // with this action left menu bar is getting opened
await page.mouse.move(1280, 0); // moving out side left menu bar to avoid random failure due to left menu bar
};
export const visitUserProfilePage = async (page: Page) => {
await page.getByTestId('dropdown-profile').click();
await page.waitForSelector('.profile-dropdown', {
state: 'visible',
});
const userResponse = page.waitForResponse(
'/api/v1/users/name/*?fields=*&include=all'
);
await page.getByTestId('user-name').click({ force: true });
await userResponse;
await clickOutside(page);
};

View File

@ -14,7 +14,6 @@
import { Col, Row } from 'antd';
import React, { useMemo } from 'react';
import { EntityField } from '../../../../../constants/Feeds.constants';
import { getTextDiff } from '../../../../../utils/EntityVersionUtils';
import {
getFeedChangeFieldLabel,
getFieldOperationIcon,
@ -25,13 +24,10 @@ import { DescriptionFeedProps } from './DescriptionFeed.interface';
function DescriptionFeed({ feed }: Readonly<DescriptionFeedProps>) {
const { message, fieldOperation } = useMemo(() => {
const diffText = getTextDiff(
feed.feedInfo?.entitySpecificInfo?.previousDescription ?? '',
feed.feedInfo?.entitySpecificInfo?.newDescription ?? ''
);
return {
message: diffText,
message: (feed.feedInfo?.entitySpecificInfo?.diffMessage ?? '').split(
':'
)[1],
fieldOperation: feed.fieldOperation,
fieldChanged: getFeedChangeFieldLabel(
feed.feedInfo?.fieldName as EntityField

View File

@ -44,7 +44,11 @@ const FeedPanelHeader: FC<FeedPanelHeaderProp> = ({
const entityField = getEntityField(entityLink);
return (
<header className={classNames('d-flex justify-between p-y-md', className)}>
<header
className={classNames(
'd-flex justify-between items-center p-y-md',
className
)}>
<p data-testid="header-title">
<span data-testid="header-noun">
{noun ? noun : getFeedPanelHeaderText(threadType)}{' '}

View File

@ -218,16 +218,6 @@ const ActivityFeedProvider = ({ children, user }: Props) => {
try {
const res = await postFeedById(id, data);
const { id: responseId, posts } = res;
setEntityThread((pre) => {
return pre.map((thread) => {
if (thread.id === responseId) {
return { ...res, posts: posts?.slice(-3) };
} else {
return thread;
}
});
});
setActiveThread(res);
} catch (error) {
showErrorToast(

View File

@ -69,12 +69,13 @@ const TagsViewer: FunctionComponent<TagsViewerProps> = ({
const readMoreRenderElement = useMemo(
() => (
<div data-testid="read-more-element">
{isOpen &&
sortedTagsBySource.slice(sizeCap).map((tag) => (
<p className="text-left" key={tag}>
{getTagsElement(tag)}
</p>
))}
{isOpen && (
<div className="m-t-xs d-flex flex-wrap gap-2">
{sortedTagsBySource
.slice(sizeCap)
.map((tag) => getTagsElement(tag))}
</div>
)}
{hasMoreElement && (
<Button
@ -94,18 +95,16 @@ const TagsViewer: FunctionComponent<TagsViewerProps> = ({
);
const popoverRenderElement = useMemo(
() => (
<div className="m-t-xs" data-testid="popover-element">
{sortedTagsBySource.slice(sizeCap).length > 0 && (
() =>
sortedTagsBySource.slice(sizeCap).length > 0 && (
<div className="m-t-xs" data-testid="popover-element">
<Popover
content={
<>
{sortedTagsBySource.slice(sizeCap).map((tag) => (
<p className="text-left" key={tag}>
{getTagsElement(tag)}
</p>
))}
</>
<div className="d-flex flex-column flex-wrap gap-2">
{sortedTagsBySource
.slice(sizeCap)
.map((tag) => getTagsElement(tag))}
</div>
}
overlayClassName="tag-popover-container"
placement="bottom"
@ -116,9 +115,8 @@ const TagsViewer: FunctionComponent<TagsViewerProps> = ({
sortedTagsBySource.length - (sizeCap ?? 0)
} more`}</Tag>
</Popover>
)}
</div>
),
</div>
),
[sizeCap, sortedTagsBySource]
);

View File

@ -1480,7 +1480,7 @@
"feed-asset-action-header": "{{action}} <0>data asset</0>",
"feed-custom-property-header": "updated Custom Properties on",
"feed-entity-action-header": "{{action}} <0>data asset</0>",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for {{entity}}",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for",
"feed-filter-all": "Feeds for all the data assets that you own and follow",
"feed-filter-following": "Feeds for all the data assets that you follow",
"feed-filter-owner": "Feeds for all the data assets that you own",

View File

@ -1565,7 +1565,7 @@
"minute": "Minute",
"modify-hierarchy-entity-description": "Modify the hierarchy by changing the Parent {{entity}}.",
"most-active-users": "Displays the most active users on the platform based on Page Views.",
"most-viewed-data-assets": "Displays the most viewed data assets.",
"most-viewed-data-assets": "Displays the most viewed feed-field-action-entity-headerdata assets.",
"mutually-exclusive-alert": "If you enable 'Mutually Exclusive' for a {{entity}}, users will be restricted to using only one {{child-entity}} to apply to a data asset. Once this option is activated, it cannot be deactivated.",
"name-of-the-bucket-dbt-files-stored": "Name of the bucket where the dbt files are stored.",
"new-conversation": "You are starting a new conversation",

View File

@ -1480,7 +1480,7 @@
"feed-asset-action-header": "{{action}} <0>data asset</0>",
"feed-custom-property-header": "updated Custom Properties on",
"feed-entity-action-header": "{{action}} <0>data asset</0>",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for {{entity}}",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for",
"feed-filter-all": "Feeds para todos los activos de datos que posee y sigue",
"feed-filter-following": "Feeds para todos los activos de datos que sigue",
"feed-filter-owner": "Feeds para todos los activos de datos que posee",

View File

@ -1480,7 +1480,7 @@
"feed-asset-action-header": "{{action}} <0>data asset</0>",
"feed-custom-property-header": "a mis à jour la propriété personnalisée le",
"feed-entity-action-header": "{{action}} <0>data asset</0>",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for {{entity}}",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for",
"feed-filter-all": "Flux de tous les actifs de données dont vous êtes propriétaire et que vous suivez",
"feed-filter-following": "Flux de tous les actifs de données que vous suivez",
"feed-filter-owner": "Flux de tous les actifs de données dont vous êtes propriétaire",

View File

@ -1480,7 +1480,7 @@
"feed-asset-action-header": "{{action}} <0>data asset</0>",
"feed-custom-property-header": "updated Custom Properties on",
"feed-entity-action-header": "{{action}} <0>data asset</0>",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for {{entity}}",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for",
"feed-filter-all": "פידים עבור כל הנכסים שאתה בעל ועוקב אחריהם",
"feed-filter-following": "פידים עבור כל הנכסים שאתה עוקב אחריהם",
"feed-filter-owner": "פידים עבור כל הנכסים שאתה בעל שלהם",

View File

@ -1480,7 +1480,7 @@
"feed-asset-action-header": "{{action}} <0>data asset</0>",
"feed-custom-property-header": "updated Custom Properties on",
"feed-entity-action-header": "{{action}} <0>data asset</0>",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for {{entity}}",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for",
"feed-filter-all": "Feeds for all the data assets that you own and follow",
"feed-filter-following": "Feeds for all the data assets that you follow",
"feed-filter-owner": "Feeds for all the data assets that you own",

View File

@ -1480,7 +1480,7 @@
"feed-asset-action-header": "{{action}} <0>data asset</0>",
"feed-custom-property-header": "updated Custom Properties on",
"feed-entity-action-header": "{{action}} <0>data asset</0>",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for {{entity}}",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0>",
"feed-filter-all": "Feeds voor alle data-assets waar je eigenaar van bent en volgt",
"feed-filter-following": "Feeds voor alle data-assets die je volgt",
"feed-filter-owner": "Feeds voor alle data-assets waar je eigenaar van bent",

View File

@ -1480,7 +1480,7 @@
"feed-asset-action-header": "{{action}} <0>data asset</0>",
"feed-custom-property-header": "updated Custom Properties on",
"feed-entity-action-header": "{{action}} <0>data asset</0>",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for {{entity}}",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for",
"feed-filter-all": "Feeds para todos os ativos de dados que você possui e segue",
"feed-filter-following": "Feeds para todos os ativos de dados que você segue",
"feed-filter-owner": "Feeds para todos os ativos de dados que você possui",

View File

@ -1480,7 +1480,7 @@
"feed-asset-action-header": "{{action}} <0>data asset</0>",
"feed-custom-property-header": "updated Custom Properties on",
"feed-entity-action-header": "{{action}} <0>data asset</0>",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for {{entity}}",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for",
"feed-filter-all": "Feeds for all the data assets that you own and follow",
"feed-filter-following": "Feeds for all the data assets that you follow",
"feed-filter-owner": "Feeds for all the data assets that you own",

View File

@ -1480,7 +1480,7 @@
"feed-asset-action-header": "{{action}} <0>data asset</0>",
"feed-custom-property-header": "updated Custom Properties on",
"feed-entity-action-header": "{{action}} <0>data asset</0>",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for {{entity}}",
"feed-field-action-entity-header": "{{action}} <0>{{field}}</0> for",
"feed-filter-all": "Feeds for all the data assets that you own and follow",
"feed-filter-following": "Feeds for all the data assets that you follow",
"feed-filter-owner": "Feeds for all the data assets that you own",

View File

@ -21,6 +21,7 @@ import {
getEntityFQN,
getEntityType,
getFeedHeaderTextFromCardStyle,
getFieldOperationIcon,
suggestions,
} from './FeedUtils';
@ -274,3 +275,27 @@ describe('getFeedHeaderTextFromCardStyle', () => {
expect(stringResult).toContain('label.updated-lowercase');
});
});
describe('getFieldOperationIcon', () => {
it('should not return icon in case of operation updated', () => {
const result = getFieldOperationIcon(FieldOperation.Updated);
expect(result).toBeUndefined();
});
it('should return icon in case of operation added', () => {
const result = getFieldOperationIcon(FieldOperation.Added);
const stringResult = JSON.stringify(result);
expect(stringResult).toContain(FieldOperation.Added);
});
it('should return icon in case of operation deleted', () => {
const result = getFieldOperationIcon(FieldOperation.Deleted);
const stringResult = JSON.stringify(result);
expect(stringResult).toContain(FieldOperation.Deleted);
});
});

View File

@ -12,6 +12,7 @@
*/
import { RightOutlined } from '@ant-design/icons';
import Icon from '@ant-design/icons/lib/components/Icon';
import { Typography } from 'antd';
import { AxiosError } from 'axios';
import { Operation } from 'fast-json-patch';
@ -640,21 +641,24 @@ export const getFeedChangeFieldLabel = (fieldName?: EntityField) => {
};
export const getFieldOperationIcon = (fieldOperation?: FieldOperation) => {
let Icon = UpdatedIcon;
let icon;
switch (fieldOperation) {
case FieldOperation.Added:
Icon = AddIcon;
icon = AddIcon;
break;
case FieldOperation.Updated:
case FieldOperation.Deleted:
Icon = UpdatedIcon;
icon = UpdatedIcon;
break;
}
return <Icon height={16} width={16} />;
return (
icon && (
<Icon component={icon} height={16} name={fieldOperation} width={16} />
)
);
};
export const getTestCaseNameListForResult = (

View File

@ -202,6 +202,7 @@ export const getEntityIcon = (
const entityIconMapping: Record<string, SvgComponent> = {
[SearchIndex.DATABASE]: DatabaseIcon,
[EntityType.DATABASE]: DatabaseIcon,
[EntityType.DATABASE_SERVICE]: DatabaseIcon,
[SearchIndex.DATABASE_SCHEMA]: SchemaIcon,
[EntityType.DATABASE_SCHEMA]: SchemaIcon,
[SearchIndex.TOPIC]: TopicIcon,
@ -252,16 +253,18 @@ export const getEntityIcon = (
[EntityType.ROLE]: RoleIcon,
[EntityType.POLICY]: PolicyIcon,
[EntityType.EVENT_SUBSCRIPTION]: AlertIcon,
[EntityType.USER]: UserIcon,
[SearchIndex.USER]: UserIcon,
[EntityType.INGESTION_PIPELINE]: PipelineIcon,
[SearchIndex.INGESTION_PIPELINE]: PipelineIcon,
[EntityType.ALERT]: AlertIcon,
['tagCategory']: ClassificationIcon,
['ingestionPipeline']: PipelineIcon,
['alert']: AlertIcon,
['announcement']: AnnouncementIcon,
['conversation']: ConversationIcon,
['task']: TaskIcon,
['dataQuality']: DataQualityIcon,
['services']: ServicesIcon,
['automator']: AutomatorBotIcon,
['user']: UserIcon,
['notification']: NotificationIcon,
};