diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.interface.ts index 9cf2606c858..3bab0acfdde 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.interface.ts @@ -44,8 +44,9 @@ export interface FeedBodyProp extends HTMLAttributes, Pick { message: string; - postId: string; - threadId: string; + postId?: string; + threadId?: string; + isAuthor: boolean; onConfirmation: (data: ConfirmState) => void; } export interface FeedFooterProp diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.test.tsx new file mode 100644 index 00000000000..1b9269dedbf --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.test.tsx @@ -0,0 +1,78 @@ +/* + * Copyright 2021 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 { findByText, queryByText, render } from '@testing-library/react'; +import { Post } from 'Models'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import ActivityFeedCard from './ActivityFeedCard'; + +jest.mock('../../../AppState', () => ({ + userDetails: { + name: '', + }, + users: [{ name: '' }], +})); + +jest.mock('../../../hooks/authHooks', () => ({ + useAuth: jest.fn().mockReturnValue({ isAdminUser: false }), +})); + +jest.mock('../../../utils/FeedUtils', () => ({ + getEntityField: jest.fn(), + getEntityFQN: jest.fn(), + getEntityType: jest.fn(), +})); + +jest.mock('../../Modals/ConfirmationModal/ConfirmationModal', () => { + return jest.fn().mockReturnValue(

ConfirmationModal

); +}); +jest.mock('../FeedCardBody/FeedCardBody', () => { + return jest.fn().mockReturnValue(

FeedCardBody

); +}); +jest.mock('../FeedCardFooter/FeedCardFooter', () => { + return jest.fn().mockReturnValue(

FeedCardFooter

); +}); +jest.mock('../FeedCardHeader/FeedCardHeader', () => { + return jest.fn().mockReturnValue(

FeedCardHeader

); +}); + +const mockFeedCardProps = { + feed: {} as Post, + replies: 0, + repliedUsers: [], + entityLink: '', + isEntityFeed: true, + threadId: '', + lastReplyTimeStamp: 1647322547179, + onThreadSelect: jest.fn(), + isFooterVisible: false, + deletePostHandler: jest.fn(), +}; + +describe('Test ActivityFeedCard Component', () => { + it('Check if ActivityFeedCard component has all child components', async () => { + const { container } = render(, { + wrapper: MemoryRouter, + }); + const feedCardHeader = await findByText(container, /FeedCardHeader/i); + const feedCardBody = await findByText(container, /FeedCardBody/i); + const feedCardFooter = await findByText(container, /FeedCardFooter/i); + const confirmationModal = queryByText(container, /ConfirmationModal/i); + + expect(feedCardHeader).toBeInTheDocument(); + expect(feedCardBody).toBeInTheDocument(); + expect(feedCardFooter).toBeInTheDocument(); + expect(confirmationModal).not.toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.tsx index 2e7978b9128..c611dc0dace 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.tsx @@ -11,268 +11,24 @@ * limitations under the License. */ -import { AxiosResponse } from 'axios'; import classNames from 'classnames'; -import { isUndefined, toLower } from 'lodash'; -import React, { FC, Fragment, useState } from 'react'; -import { Link } from 'react-router-dom'; +import React, { FC, useState } from 'react'; import AppState from '../../../AppState'; -import { getUserByName } from '../../../axiosAPIs/userAPI'; -import { EntityType, TabSpecificField } from '../../../enums/entity.enum'; -import { User } from '../../../generated/entity/teams/user'; -import { getPartialNameFromFQN } from '../../../utils/CommonUtils'; +import { useAuth } from '../../../hooks/authHooks'; import { getEntityField, getEntityFQN, getEntityType, - getFrontEndFormat, - getReplyText, } from '../../../utils/FeedUtils'; -import SVGIcons, { Icons } from '../../../utils/SvgUtils'; -import { getEntityLink } from '../../../utils/TableUtils'; -import { getDayTimeByTimeStamp } from '../../../utils/TimeUtils'; -import Avatar from '../../common/avatar/Avatar'; -import PopOver from '../../common/popover/PopOver'; -import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer'; -import Loader from '../../Loader/Loader'; import ConfirmationModal from '../../Modals/ConfirmationModal/ConfirmationModal'; +import FeedCardBody from '../FeedCardBody/FeedCardBody'; +import FeedCardFooter from '../FeedCardFooter/FeedCardFooter'; +import FeedCardHeader from '../FeedCardHeader/FeedCardHeader'; import { ActivityFeedCardProp, ConfirmState, - FeedBodyProp, - FeedFooterProp, - FeedHeaderProp, } from './ActivityFeedCard.interface'; -const FeedHeader: FC = ({ - className, - createdBy, - timeStamp, - entityFQN, - entityType, - entityField, - isEntityFeed, -}) => { - const [userData, setUserData] = useState({} as User); - const [isLoading, setIsLoading] = useState(true); - const [isError, setIsError] = useState(false); - - const onMousEnterHandler = () => { - getUserByName(createdBy, 'profile,roles,teams,follows,owns') - .then((res: AxiosResponse) => { - setUserData(res.data); - }) - .catch(() => { - setIsError(true); - }) - .finally(() => setIsLoading(false)); - }; - - const getUserData = () => { - const displayName = userData.displayName ?? ''; - const name = userData.name ?? ''; - const teams = userData.teams; - const roles = userData.roles; - - return ( - - {isError ? ( -

Error while getting user data.

- ) : ( -
- {isLoading ? ( - - ) : ( -
-
-
- -
-
-

- - {displayName} - - {name} -

-
-
-
- {teams?.length || roles?.length ? ( -
- ) : null} - {teams?.length ? ( -

- - - Teams - - - {teams.map((team, i) => ( - - {team?.displayName ?? team?.name} - - ))} - -

- ) : null} - {roles?.length ? ( -

- - - Roles - - - {roles.map((role, i) => ( - - {role?.displayName ?? role?.name} - - ))} - -

- ) : null} -
-
- )} -
- )} -
- ); - }; - - return ( -
- - - - - -
- {createdBy} - {entityFQN && entityType ? ( - - posted on{' '} - {isEntityFeed ? ( - {entityField} - ) : ( - - {entityType}{' '} - - - - - )} - - ) : null} - - {getDayTimeByTimeStamp(timeStamp)} - -
-
- ); -}; - -const FeedBody: FC = ({ - message, - className, - threadId, - postId, - deletePostHandler, - onConfirmation, -}) => { - return ( - -
- - {threadId && postId && deletePostHandler ? ( - onConfirmation({ state: true, postId, threadId })}> - - - ) : null} -
-
- ); -}; - -export const FeedFooter: FC = ({ - repliedUsers, - replies, - className, - threadId, - onThreadSelect, - lastReplyTimeStamp, - isFooterVisible, -}) => { - const repliesCount = isUndefined(replies) ? 0 : replies; - - return ( -
- {!isUndefined(repliedUsers) && - !isUndefined(replies) && - isFooterVisible ? ( -
- {repliedUsers?.map((u, i) => ( - - ))} -

onThreadSelect?.(threadId as string)}> - {getReplyText(repliesCount)} -

- {lastReplyTimeStamp && repliesCount > 0 ? ( - - Last reply{' '} - {toLower(getDayTimeByTimeStamp(lastReplyTimeStamp as number))} - - ) : null} -
- ) : null} -
- ); -}; - const ActivityFeedCard: FC = ({ feed, className, @@ -290,6 +46,9 @@ const ActivityFeedCard: FC = ({ const entityFQN = getEntityFQN(entityLink as string); const entityField = getEntityField(entityLink as string); + const { isAdminUser } = useAuth(); + const currentUser = AppState.userDetails?.name ?? AppState.users[0]?.name; + const [confirmationState, setConfirmationState] = useState({ state: false, threadId: undefined, @@ -317,7 +76,7 @@ const ActivityFeedCard: FC = ({ return (
- = ({ isEntityFeed={isEntityFeed} timeStamp={feed.postTs} /> - - = ({ {postLength > 1 ? (
- = ({ const onThreadIdSelect = (id: string) => { setSelctedThreadId(id); + setSelectedThread(undefined); }; const onThreadIdDeselect = () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel.tsx index bf26df2af09..2b6ae2fc04b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel.tsx @@ -23,15 +23,14 @@ import { getFeedListWithRelativeDays, getReplyText, } from '../../../utils/FeedUtils'; -import ActivityFeedCard, { - FeedFooter, -} from '../ActivityFeedCard/ActivityFeedCard'; +import ActivityFeedCard from '../ActivityFeedCard/ActivityFeedCard'; import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor'; import { FeedListSeparator } from '../ActivityFeedList/ActivityFeedList'; import { FeedPanelHeader, FeedPanelOverlay, } from '../ActivityFeedPanel/ActivityFeedPanel'; +import FeedCardFooter from '../FeedCardFooter/FeedCardFooter'; import { ActivityThreadListProp, ActivityThreadPanelProp, @@ -88,7 +87,7 @@ const ActivityThreadList: FC = ({ {postLength > 1 ? (
- ({ + getFrontEndFormat: jest.fn(), +})); + +jest.mock('../../common/rich-text-editor/RichTextEditorPreviewer', () => { + return jest.fn().mockReturnValue(

RichText Preview

); +}); + +const mockFeedCardBodyProps = { + isAuthor: true, + message: 'xyz', + threadId: 'id1', + postId: 'id2', + deletePostHandler: jest.fn(), + onConfirmation: jest.fn(), +}; + +describe('Test FeedCardBody component', () => { + it('Check if FeedCardBody has all the child elements', async () => { + const { container } = render(, { + wrapper: MemoryRouter, + }); + + const messagePreview = await findByText(container, /RichText Preview/i); + const deleteButton = await findByTestId(container, 'delete-button'); + + expect(messagePreview).toBeInTheDocument(); + expect(deleteButton).toBeInTheDocument(); + }); + + it('Check if FeedCardBody has isAuthor as false', async () => { + const { container } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const deleteButton = queryByTestId(container, 'delete-button'); + + expect(deleteButton).not.toBeInTheDocument(); + }); + + it('Check if FeedCardBody has deletePostHandler as undefined', async () => { + const { container } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const deleteButton = queryByTestId(container, 'delete-button'); + + expect(deleteButton).not.toBeInTheDocument(); + }); + + it('Check if FeedCardBody has postId as undefined', async () => { + const { container } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const deleteButton = queryByTestId(container, 'delete-button'); + + expect(deleteButton).not.toBeInTheDocument(); + }); + + it('Check if FeedCardBody has threadId as undefined', async () => { + const { container } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const deleteButton = queryByTestId(container, 'delete-button'); + + expect(deleteButton).not.toBeInTheDocument(); + }); + + it('Check if FeedCardBody has postId, threadId and deletePostHandler as undefined and isAuthor as false', async () => { + const { container } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const deleteButton = queryByTestId(container, 'delete-button'); + + expect(deleteButton).not.toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardBody/FeedCardBody.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardBody/FeedCardBody.tsx new file mode 100644 index 00000000000..2af93be7631 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardBody/FeedCardBody.tsx @@ -0,0 +1,51 @@ +/* + * Copyright 2021 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 classNames from 'classnames'; +import React, { FC, Fragment } from 'react'; +import { getFrontEndFormat } from '../../../utils/FeedUtils'; +import SVGIcons, { Icons } from '../../../utils/SvgUtils'; +import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer'; +import { FeedBodyProp } from '../ActivityFeedCard/ActivityFeedCard.interface'; + +const FeedCardBody: FC = ({ + isAuthor, + message, + className, + threadId, + postId, + deletePostHandler, + onConfirmation, +}) => { + return ( + +
+ + {threadId && postId && deletePostHandler && isAuthor ? ( + onConfirmation({ state: true, postId, threadId })}> + + + ) : null} +
+
+ ); +}; + +export default FeedCardBody; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardFooter/FeedCardFooter.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardFooter/FeedCardFooter.test.tsx new file mode 100644 index 00000000000..838d99a4fff --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardFooter/FeedCardFooter.test.tsx @@ -0,0 +1,135 @@ +/* + * Copyright 2021 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 { + findAllByTestId, + findByTestId, + queryAllByTestId, + queryByTestId, + render, +} from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import FeedCardFooter from './FeedCardFooter'; + +jest.mock('../../../utils/FeedUtils', () => ({ + getReplyText: jest.fn(), +})); + +jest.mock('../../../utils/TimeUtils', () => ({ + getDayTimeByTimeStamp: jest.fn(), +})); + +jest.mock('../../common/avatar/Avatar', () => { + return jest.fn().mockReturnValue(

Avatar

); +}); + +const mockFeedCardFooterPorps = { + repliedUsers: ['xyz', 'pqr'], + replies: 2, + threadId: 'id1', + onThreadSelect: jest.fn(), + lastReplyTimeStamp: 1647322547179, + isFooterVisible: true, +}; + +describe('Test FeedCardFooter component', () => { + it('Check if FeedCardFooter has all child elements', async () => { + const { container } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const repliedUsers = await findAllByTestId(container, 'replied-user'); + const replyCount = await findByTestId(container, 'reply-count'); + const lastReply = await findByTestId(container, 'last-reply'); + + expect(repliedUsers).toHaveLength(2); + expect(replyCount).toBeInTheDocument(); + expect(lastReply).toBeInTheDocument(); + }); + + it('Check if FeedCardFooter has isFooterVisible as false', async () => { + const { container } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const repliedUsers = queryAllByTestId(container, 'replied-user'); + const replyCount = queryByTestId(container, 'reply-count'); + const lastReply = queryByTestId(container, 'last-reply'); + + expect(repliedUsers).toHaveLength(0); + expect(replyCount).not.toBeInTheDocument(); + expect(lastReply).not.toBeInTheDocument(); + }); + + it('Check if FeedCardFooter has repliedUsers as undefined', async () => { + const { container } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const repliedUsers = queryAllByTestId(container, 'replied-user'); + const replyCount = queryByTestId(container, 'reply-count'); + const lastReply = queryByTestId(container, 'last-reply'); + + expect(repliedUsers).toHaveLength(0); + expect(replyCount).not.toBeInTheDocument(); + expect(lastReply).not.toBeInTheDocument(); + }); + + it('Check if FeedCardFooter has replies as undefined', async () => { + const { container } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const repliedUsers = queryAllByTestId(container, 'replied-user'); + const replyCount = queryByTestId(container, 'reply-count'); + const lastReply = queryByTestId(container, 'last-reply'); + + expect(repliedUsers).toHaveLength(0); + expect(replyCount).not.toBeInTheDocument(); + expect(lastReply).not.toBeInTheDocument(); + }); + + it('Check if FeedCardFooter has lastReplyTimeStamp as undefined and replies as 0', async () => { + const { container } = render( + , + { + wrapper: MemoryRouter, + } + ); + + const repliedUsers = queryAllByTestId(container, 'replied-user'); + const replyCount = queryByTestId(container, 'reply-count'); + const lastReply = queryByTestId(container, 'last-reply'); + + expect(repliedUsers).toHaveLength(2); + expect(replyCount).toBeInTheDocument(); + expect(lastReply).not.toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardFooter/FeedCardFooter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardFooter/FeedCardFooter.tsx new file mode 100644 index 00000000000..3d6c8d85e05 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardFooter/FeedCardFooter.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2021 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 { isUndefined, toLower } from 'lodash'; +import React, { FC } from 'react'; +import { getReplyText } from '../../../utils/FeedUtils'; +import { getDayTimeByTimeStamp } from '../../../utils/TimeUtils'; +import Avatar from '../../common/avatar/Avatar'; +import { FeedFooterProp } from '../ActivityFeedCard/ActivityFeedCard.interface'; + +const FeedCardFooter: FC = ({ + repliedUsers, + replies, + className, + threadId, + onThreadSelect, + lastReplyTimeStamp, + isFooterVisible, +}) => { + const repliesCount = isUndefined(replies) ? 0 : replies; + + return ( +
+ {!isUndefined(repliedUsers) && + !isUndefined(replies) && + isFooterVisible ? ( +
+ {repliedUsers?.map((u, i) => ( + + ))} +

onThreadSelect?.(threadId as string)}> + {getReplyText(repliesCount)} +

+ {lastReplyTimeStamp && repliesCount > 0 ? ( + + Last reply{' '} + {toLower(getDayTimeByTimeStamp(lastReplyTimeStamp as number))} + + ) : null} +
+ ) : null} +
+ ); +}; + +export default FeedCardFooter; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardHeader/FeedCardHeader.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardHeader/FeedCardHeader.test.tsx new file mode 100644 index 00000000000..a50496fcbb2 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardHeader/FeedCardHeader.test.tsx @@ -0,0 +1,104 @@ +/* + * Copyright 2021 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 { + findByTestId, + findByText, + queryByTestId, + render, +} from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import FeedCardHeader from './FeedCardHeader'; + +jest.mock('../../../axiosAPIs/userAPI', () => ({ + getUserByName: jest.fn().mockReturnValue({}), +})); + +jest.mock('../../../utils/CommonUtils', () => ({ + getPartialNameFromFQN: jest.fn().mockReturnValue('feedcard'), +})); + +jest.mock('../../../utils/TableUtils', () => ({ + getEntityLink: jest.fn(), +})); + +jest.mock('../../../utils/TimeUtils', () => ({ + getDayTimeByTimeStamp: jest.fn(), +})); + +jest.mock('../../common/avatar/Avatar', () => { + return jest.fn().mockReturnValue(

Avatar

); +}); + +const mockFeedHeaderProps = { + createdBy: 'xyz', + entityFQN: 'x.y.z', + entityField: 'z', + entityType: 'y', + isEntityFeed: true, + timeStamp: 1647322547179, +}; + +describe('Test Feedheader Component', () => { + it('Checks if the Feedheader component has isEntityFeed as true', async () => { + const { container } = render(, { + 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(createdBy).toBeInTheDocument(); + + expect(headerElement).toBeInTheDocument(); + expect(entityFieldElement).toBeInTheDocument(); + expect(entityTypeElement).not.toBeInTheDocument(); + expect(entityLinkElement).not.toBeInTheDocument(); + expect(timeStampElement).toBeInTheDocument(); + }); + + it('Checks if the Feedheader component has isEntityFeed as false', async () => { + const { container } = render( + , + { + wrapper: MemoryRouter, + } + ); + const createdBy = await findByText(container, /xyz/i); + + 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(createdBy).toBeInTheDocument(); + + expect(headerElement).toBeInTheDocument(); + expect(entityFieldElement).not.toBeInTheDocument(); + expect(entityTypeElement).toBeInTheDocument(); + expect(entityLinkElement).toBeInTheDocument(); + expect(timeStampElement).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardHeader/FeedCardHeader.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardHeader/FeedCardHeader.tsx new file mode 100644 index 00000000000..ecfc8f2c1c3 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/FeedCardHeader/FeedCardHeader.tsx @@ -0,0 +1,197 @@ +/* + * Copyright 2021 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 { AxiosResponse } from 'axios'; +import classNames from 'classnames'; +import React, { FC, Fragment, useState } from 'react'; +import { Link } from 'react-router-dom'; +import AppState from '../../../AppState'; +import { getUserByName } from '../../../axiosAPIs/userAPI'; +import { EntityType, TabSpecificField } from '../../../enums/entity.enum'; +import { User } from '../../../generated/entity/teams/user'; +import { getPartialNameFromFQN } from '../../../utils/CommonUtils'; +import SVGIcons, { Icons } from '../../../utils/SvgUtils'; +import { getEntityLink } from '../../../utils/TableUtils'; +import { getDayTimeByTimeStamp } from '../../../utils/TimeUtils'; +import Avatar from '../../common/avatar/Avatar'; +import PopOver from '../../common/popover/PopOver'; +import Loader from '../../Loader/Loader'; +import { FeedHeaderProp } from '../ActivityFeedCard/ActivityFeedCard.interface'; + +const FeedCardHeader: FC = ({ + className, + createdBy, + timeStamp, + entityFQN, + entityType, + entityField, + isEntityFeed, +}) => { + const [userData, setUserData] = useState({} as User); + const [isLoading, setIsLoading] = useState(true); + const [isError, setIsError] = useState(false); + + const onMousEnterHandler = () => { + getUserByName(createdBy, 'profile,roles,teams,follows,owns') + .then((res: AxiosResponse) => { + setUserData(res.data); + }) + .catch(() => { + setIsError(true); + }) + .finally(() => setIsLoading(false)); + }; + + const getUserData = () => { + const displayName = userData.displayName ?? ''; + const name = userData.name ?? ''; + const teams = userData.teams; + const roles = userData.roles; + + return ( + + {isError ? ( +

Error while getting user data.

+ ) : ( +
+ {isLoading ? ( + + ) : ( +
+
+
+ +
+
+

+ + {displayName} + + {name} +

+
+
+
+ {teams?.length || roles?.length ? ( +
+ ) : null} + {teams?.length ? ( +

+ + + Teams + + + {teams.map((team, i) => ( + + {team?.displayName ?? team?.name} + + ))} + +

+ ) : null} + {roles?.length ? ( +

+ + + Roles + + + {roles.map((role, i) => ( + + {role?.displayName ?? role?.name} + + ))} + +

+ ) : null} +
+
+ )} +
+ )} +
+ ); + }; + + return ( +
+ + + + + +
+ {createdBy} + {entityFQN && entityType ? ( + + posted on{' '} + {isEntityFeed ? ( + + {entityField} + + ) : ( + + {entityType} + + + + + )} + + ) : null} + + {getDayTimeByTimeStamp(timeStamp)} + +
+
+ ); +}; + +export default FeedCardHeader; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MyData/MyData.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MyData/MyData.component.tsx index 007962a8575..51c7f56f4f7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MyData/MyData.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MyData/MyData.component.tsx @@ -50,6 +50,7 @@ const MyData: React.FC = ({ feedFilterHandler, isFeedLoading, postFeedHandler, + deletePostHandler, }: MyDataProps): React.ReactElement => { const [fieldListVisible, setFieldListVisible] = useState(false); const isMounted = useRef(false); @@ -177,6 +178,7 @@ const MyData: React.FC = ({ void; + deletePostHandler?: (threadId: string, postId: string) => void; } diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/MyDataPage/MyDataPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/MyDataPage/MyDataPage.component.tsx index a9fb0bf4b51..a2b0a1150ae 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/MyDataPage/MyDataPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/MyDataPage/MyDataPage.component.tsx @@ -24,7 +24,11 @@ import React, { useEffect, useState } from 'react'; import { useLocation } from 'react-router-dom'; import AppState from '../../AppState'; import { getAirflowPipelines } from '../../axiosAPIs/airflowPipelineAPI'; -import { getFeedsWithFilter, postFeedById } from '../../axiosAPIs/feedsAPI'; +import { + deletePostById, + getFeedsWithFilter, + postFeedById, +} from '../../axiosAPIs/feedsAPI'; import { searchData } from '../../axiosAPIs/miscAPI'; import PageContainerV1 from '../../components/containers/PageContainerV1'; import Loader from '../../components/Loader/Loader'; @@ -176,6 +180,29 @@ const MyDataPage = () => { }); }; + const deletePostHandler = (threadId: string, postId: string) => { + deletePostById(threadId, postId) + .then((res: AxiosResponse) => { + if (res.data) { + const { id } = res.data; + setEntityThread((pre) => { + return pre.map((thread) => { + const posts = thread.posts.filter((post) => post.id !== id); + + return { ...thread, posts: posts }; + }); + }); + showToast({ + variant: 'success', + body: 'Post got deleted successfully', + }); + } + }) + .catch(() => { + showToast({ variant: 'error', body: 'Error while deleting post' }); + }); + }; + useEffect(() => { fetchData(true); }, []); @@ -202,6 +229,7 @@ const MyDataPage = () => { !isLoading ? ( { key: 'Owner', value: database?.owner?.type === 'team' - ? getTeamDetailsPath(database?.owner?.type || '') + ? getTeamDetailsPath( + database?.owner?.displayName || database?.owner?.name || '' + ) : database?.owner?.displayName || database?.owner?.name || '', placeholderText: database?.owner?.displayName || database?.owner?.name || '',