mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-19 14:37:52 +00:00
Add check to allow only the owner and admin to delete the post. (#3422)
* Add check to allow only the owner and admin to delete the post. * Change author prop to isAuthor prop * Add unit tests for feedcard component * Add group-hover for feedcard * Add delete posts support in homepage
This commit is contained in:
parent
a6e2ee78c4
commit
c860177a49
@ -44,8 +44,9 @@ export interface FeedBodyProp
|
||||
extends HTMLAttributes<HTMLDivElement>,
|
||||
Pick<ActivityFeedCardProp, 'deletePostHandler'> {
|
||||
message: string;
|
||||
postId: string;
|
||||
threadId: string;
|
||||
postId?: string;
|
||||
threadId?: string;
|
||||
isAuthor: boolean;
|
||||
onConfirmation: (data: ConfirmState) => void;
|
||||
}
|
||||
export interface FeedFooterProp
|
||||
|
@ -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(<p>ConfirmationModal</p>);
|
||||
});
|
||||
jest.mock('../FeedCardBody/FeedCardBody', () => {
|
||||
return jest.fn().mockReturnValue(<p>FeedCardBody</p>);
|
||||
});
|
||||
jest.mock('../FeedCardFooter/FeedCardFooter', () => {
|
||||
return jest.fn().mockReturnValue(<p>FeedCardFooter</p>);
|
||||
});
|
||||
jest.mock('../FeedCardHeader/FeedCardHeader', () => {
|
||||
return jest.fn().mockReturnValue(<p>FeedCardHeader</p>);
|
||||
});
|
||||
|
||||
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(<ActivityFeedCard {...mockFeedCardProps} />, {
|
||||
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();
|
||||
});
|
||||
});
|
@ -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<FeedHeaderProp> = ({
|
||||
className,
|
||||
createdBy,
|
||||
timeStamp,
|
||||
entityFQN,
|
||||
entityType,
|
||||
entityField,
|
||||
isEntityFeed,
|
||||
}) => {
|
||||
const [userData, setUserData] = useState<User>({} 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 (
|
||||
<Fragment>
|
||||
{isError ? (
|
||||
<p>Error while getting user data.</p>
|
||||
) : (
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<Loader size="small" />
|
||||
) : (
|
||||
<div>
|
||||
<div className="tw-flex">
|
||||
<div className="tw-mr-2">
|
||||
<Avatar name={createdBy} type="square" width="30" />
|
||||
</div>
|
||||
<div className="tw-self-center">
|
||||
<p>
|
||||
<span className="tw-font-medium tw-mr-2">
|
||||
{displayName}
|
||||
</span>
|
||||
<span className="tw-text-grey-muted">{name}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tw-text-left">
|
||||
{teams?.length || roles?.length ? (
|
||||
<hr className="tw-my-2 tw--mx-3" />
|
||||
) : null}
|
||||
{teams?.length ? (
|
||||
<p className="tw-mt-2">
|
||||
<SVGIcons
|
||||
alt="icon"
|
||||
className="tw-w-4"
|
||||
icon={Icons.TEAMS_GREY}
|
||||
/>
|
||||
<span className="tw-mr-2 tw-ml-1 tw-align-middle tw-font-medium">
|
||||
Teams
|
||||
</span>
|
||||
<span>
|
||||
{teams.map((team, i) => (
|
||||
<span
|
||||
className="tw-bg-gray-200 tw-rounded tw-px-1 tw-text-grey-body tw-m-0.5 tw-text-xs"
|
||||
key={i}>
|
||||
{team?.displayName ?? team?.name}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</p>
|
||||
) : null}
|
||||
{roles?.length ? (
|
||||
<p className="tw-mt-2">
|
||||
<SVGIcons
|
||||
alt="icon"
|
||||
className="tw-w-4"
|
||||
icon={Icons.USERS}
|
||||
/>
|
||||
<span className="tw-mr-2 tw-ml-1 tw-align-middle tw-font-medium">
|
||||
Roles
|
||||
</span>
|
||||
<span>
|
||||
{roles.map((role, i) => (
|
||||
<span
|
||||
className="tw-bg-gray-200 tw-rounded tw-px-1 tw-text-grey-body tw-m-0.5 tw-text-xs"
|
||||
key={i}>
|
||||
{role?.displayName ?? role?.name}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames('tw-flex tw-mb-1.5', className)}>
|
||||
<PopOver
|
||||
hideDelay={500}
|
||||
html={getUserData()}
|
||||
position="top"
|
||||
theme="light"
|
||||
trigger="mouseenter">
|
||||
<span className="tw-cursor-pointer" onMouseEnter={onMousEnterHandler}>
|
||||
<Avatar name={createdBy} type="square" width="30" />
|
||||
</span>
|
||||
</PopOver>
|
||||
<h6 className="tw-flex tw-items-center tw-m-0 tw-heading tw-pl-2">
|
||||
{createdBy}
|
||||
{entityFQN && entityType ? (
|
||||
<span className="tw-pl-1 tw-font-normal">
|
||||
posted on{' '}
|
||||
{isEntityFeed ? (
|
||||
<span className="tw-heading">{entityField}</span>
|
||||
) : (
|
||||
<Fragment>
|
||||
{entityType}{' '}
|
||||
<Link
|
||||
to={`${getEntityLink(
|
||||
entityType as string,
|
||||
entityFQN as string
|
||||
)}${
|
||||
entityType !== EntityType.WEBHOOK
|
||||
? `/${TabSpecificField.ACTIVITY_FEED}`
|
||||
: ''
|
||||
}`}>
|
||||
<button className="link-text" disabled={AppState.isTourOpen}>
|
||||
{getPartialNameFromFQN(
|
||||
entityFQN as string,
|
||||
entityType === 'table' ? ['table'] : ['database']
|
||||
) || entityFQN}
|
||||
</button>
|
||||
</Link>
|
||||
</Fragment>
|
||||
)}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="tw-text-grey-muted tw-pl-2 tw-text-xs">
|
||||
{getDayTimeByTimeStamp(timeStamp)}
|
||||
</span>
|
||||
</h6>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const FeedBody: FC<FeedBodyProp> = ({
|
||||
message,
|
||||
className,
|
||||
threadId,
|
||||
postId,
|
||||
deletePostHandler,
|
||||
onConfirmation,
|
||||
}) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={className}>
|
||||
<RichTextEditorPreviewer
|
||||
className="activity-feed-card-text"
|
||||
enableSeeMoreVariant={false}
|
||||
markdown={getFrontEndFormat(message)}
|
||||
/>
|
||||
{threadId && postId && deletePostHandler ? (
|
||||
<span
|
||||
className="tw-opacity-0 hover:tw-opacity-100 tw-cursor-pointer"
|
||||
onClick={() => onConfirmation({ state: true, postId, threadId })}>
|
||||
<SVGIcons alt="delete" icon={Icons.DELETE} width="12px" />
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export const FeedFooter: FC<FeedFooterProp> = ({
|
||||
repliedUsers,
|
||||
replies,
|
||||
className,
|
||||
threadId,
|
||||
onThreadSelect,
|
||||
lastReplyTimeStamp,
|
||||
isFooterVisible,
|
||||
}) => {
|
||||
const repliesCount = isUndefined(replies) ? 0 : replies;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{!isUndefined(repliedUsers) &&
|
||||
!isUndefined(replies) &&
|
||||
isFooterVisible ? (
|
||||
<div className="tw-flex tw-group">
|
||||
{repliedUsers?.map((u, i) => (
|
||||
<Avatar
|
||||
className="tw-mt-0.5 tw-mx-0.5"
|
||||
key={i}
|
||||
name={u}
|
||||
type="square"
|
||||
width="22"
|
||||
/>
|
||||
))}
|
||||
<p
|
||||
className="tw-ml-1 link-text tw-text-xs tw-mt-1.5 tw-underline"
|
||||
onClick={() => onThreadSelect?.(threadId as string)}>
|
||||
{getReplyText(repliesCount)}
|
||||
</p>
|
||||
{lastReplyTimeStamp && repliesCount > 0 ? (
|
||||
<span className="tw-text-grey-muted tw-pl-2 tw-text-xs tw-font-medium tw-mt-1.5">
|
||||
Last reply{' '}
|
||||
{toLower(getDayTimeByTimeStamp(lastReplyTimeStamp as number))}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ActivityFeedCard: FC<ActivityFeedCardProp> = ({
|
||||
feed,
|
||||
className,
|
||||
@ -290,6 +46,9 @@ const ActivityFeedCard: FC<ActivityFeedCardProp> = ({
|
||||
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<ConfirmState>({
|
||||
state: false,
|
||||
threadId: undefined,
|
||||
@ -317,7 +76,7 @@ const ActivityFeedCard: FC<ActivityFeedCardProp> = ({
|
||||
|
||||
return (
|
||||
<div className={classNames(className)}>
|
||||
<FeedHeader
|
||||
<FeedCardHeader
|
||||
createdBy={feed.from}
|
||||
entityFQN={entityFQN as string}
|
||||
entityField={entityField as string}
|
||||
@ -325,15 +84,16 @@ const ActivityFeedCard: FC<ActivityFeedCardProp> = ({
|
||||
isEntityFeed={isEntityFeed}
|
||||
timeStamp={feed.postTs}
|
||||
/>
|
||||
<FeedBody
|
||||
<FeedCardBody
|
||||
className="tw-mx-7 tw-ml-9 tw-bg-white tw-p-3 tw-border tw-border-main tw-rounded-md tw-break-all tw-flex tw-justify-between "
|
||||
deletePostHandler={deletePostHandler}
|
||||
isAuthor={Boolean(feed.from === currentUser || isAdminUser)}
|
||||
message={feed.message}
|
||||
postId={feed.id}
|
||||
threadId={threadId as string}
|
||||
onConfirmation={onConfirmation}
|
||||
/>
|
||||
<FeedFooter
|
||||
<FeedCardFooter
|
||||
className="tw-ml-9 tw-mt-3"
|
||||
isFooterVisible={isFooterVisible}
|
||||
lastReplyTimeStamp={lastReplyTimeStamp}
|
||||
|
@ -17,11 +17,10 @@ import { EntityThread } from 'Models';
|
||||
import React, { FC, Fragment, useEffect, useState } from 'react';
|
||||
import { withLoader } from '../../../hoc/withLoader';
|
||||
import { getFeedListWithRelativeDays } from '../../../utils/FeedUtils';
|
||||
import ActivityFeedCard, {
|
||||
FeedFooter,
|
||||
} from '../ActivityFeedCard/ActivityFeedCard';
|
||||
import ActivityFeedCard from '../ActivityFeedCard/ActivityFeedCard';
|
||||
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
|
||||
import ActivityFeedPanel from '../ActivityFeedPanel/ActivityFeedPanel';
|
||||
import FeedCardFooter from '../FeedCardFooter/FeedCardFooter';
|
||||
import NoFeedPlaceholder from '../NoFeedPlaceholder/NoFeedPlaceholder';
|
||||
import {
|
||||
ActivityFeedListProp,
|
||||
@ -87,7 +86,7 @@ const FeedListBody: FC<FeedListBodyProp> = ({
|
||||
{postLength > 1 ? (
|
||||
<div className="tw-mb-6">
|
||||
<div className="tw-ml-9 tw-flex tw-mb-6">
|
||||
<FeedFooter
|
||||
<FeedCardFooter
|
||||
isFooterVisible
|
||||
className="tw--mt-4"
|
||||
lastReplyTimeStamp={lastPost?.postTs}
|
||||
@ -159,6 +158,7 @@ const ActivityFeedList: FC<ActivityFeedListProp> = ({
|
||||
|
||||
const onThreadIdSelect = (id: string) => {
|
||||
setSelctedThreadId(id);
|
||||
setSelectedThread(undefined);
|
||||
};
|
||||
|
||||
const onThreadIdDeselect = () => {
|
||||
|
@ -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<ActivityThreadListProp> = ({
|
||||
{postLength > 1 ? (
|
||||
<div className="tw-mb-6">
|
||||
<div className="tw-ml-9 tw-flex tw-mb-6">
|
||||
<FeedFooter
|
||||
<FeedCardFooter
|
||||
isFooterVisible
|
||||
className="tw--mt-4"
|
||||
lastReplyTimeStamp={lastPost?.postTs}
|
||||
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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 FeedCardBody from './FeedCardBody';
|
||||
|
||||
jest.mock('../../../utils/FeedUtils', () => ({
|
||||
getFrontEndFormat: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../common/rich-text-editor/RichTextEditorPreviewer', () => {
|
||||
return jest.fn().mockReturnValue(<p>RichText Preview</p>);
|
||||
});
|
||||
|
||||
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(<FeedCardBody {...mockFeedCardBodyProps} />, {
|
||||
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(
|
||||
<FeedCardBody {...mockFeedCardBodyProps} isAuthor={false} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const deleteButton = queryByTestId(container, 'delete-button');
|
||||
|
||||
expect(deleteButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Check if FeedCardBody has deletePostHandler as undefined', async () => {
|
||||
const { container } = render(
|
||||
<FeedCardBody {...mockFeedCardBodyProps} deletePostHandler={undefined} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const deleteButton = queryByTestId(container, 'delete-button');
|
||||
|
||||
expect(deleteButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Check if FeedCardBody has postId as undefined', async () => {
|
||||
const { container } = render(
|
||||
<FeedCardBody {...mockFeedCardBodyProps} postId={undefined} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const deleteButton = queryByTestId(container, 'delete-button');
|
||||
|
||||
expect(deleteButton).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Check if FeedCardBody has threadId as undefined', async () => {
|
||||
const { container } = render(
|
||||
<FeedCardBody {...mockFeedCardBodyProps} threadId={undefined} />,
|
||||
{
|
||||
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(
|
||||
<FeedCardBody {...mockFeedCardBodyProps} threadId={undefined} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const deleteButton = queryByTestId(container, 'delete-button');
|
||||
|
||||
expect(deleteButton).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -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<FeedBodyProp> = ({
|
||||
isAuthor,
|
||||
message,
|
||||
className,
|
||||
threadId,
|
||||
postId,
|
||||
deletePostHandler,
|
||||
onConfirmation,
|
||||
}) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classNames('tw-group', className)}>
|
||||
<RichTextEditorPreviewer
|
||||
className="activity-feed-card-text"
|
||||
enableSeeMoreVariant={false}
|
||||
markdown={getFrontEndFormat(message)}
|
||||
/>
|
||||
{threadId && postId && deletePostHandler && isAuthor ? (
|
||||
<span
|
||||
className="tw-opacity-0 group-hover:tw-opacity-100 tw-cursor-pointer"
|
||||
data-testid="delete-button"
|
||||
onClick={() => onConfirmation({ state: true, postId, threadId })}>
|
||||
<SVGIcons alt="delete" icon={Icons.DELETE} width="12px" />
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeedCardBody;
|
@ -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(<p data-testid="replied-user">Avatar</p>);
|
||||
});
|
||||
|
||||
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(
|
||||
<FeedCardFooter {...mockFeedCardFooterPorps} />,
|
||||
{
|
||||
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(
|
||||
<FeedCardFooter {...mockFeedCardFooterPorps} isFooterVisible={false} />,
|
||||
{
|
||||
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(
|
||||
<FeedCardFooter {...mockFeedCardFooterPorps} repliedUsers={undefined} />,
|
||||
{
|
||||
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(
|
||||
<FeedCardFooter {...mockFeedCardFooterPorps} replies={undefined} />,
|
||||
{
|
||||
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(
|
||||
<FeedCardFooter
|
||||
{...mockFeedCardFooterPorps}
|
||||
lastReplyTimeStamp={undefined}
|
||||
replies={0}
|
||||
/>,
|
||||
{
|
||||
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();
|
||||
});
|
||||
});
|
@ -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<FeedFooterProp> = ({
|
||||
repliedUsers,
|
||||
replies,
|
||||
className,
|
||||
threadId,
|
||||
onThreadSelect,
|
||||
lastReplyTimeStamp,
|
||||
isFooterVisible,
|
||||
}) => {
|
||||
const repliesCount = isUndefined(replies) ? 0 : replies;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{!isUndefined(repliedUsers) &&
|
||||
!isUndefined(replies) &&
|
||||
isFooterVisible ? (
|
||||
<div className="tw-flex tw-group">
|
||||
{repliedUsers?.map((u, i) => (
|
||||
<Avatar
|
||||
className="tw-mt-0.5 tw-mx-0.5"
|
||||
data-testid="replied-user"
|
||||
key={i}
|
||||
name={u}
|
||||
type="square"
|
||||
width="22"
|
||||
/>
|
||||
))}
|
||||
<p
|
||||
className="tw-ml-1 link-text tw-text-xs tw-mt-1.5 tw-underline"
|
||||
data-testid="reply-count"
|
||||
onClick={() => onThreadSelect?.(threadId as string)}>
|
||||
{getReplyText(repliesCount)}
|
||||
</p>
|
||||
{lastReplyTimeStamp && repliesCount > 0 ? (
|
||||
<span
|
||||
className="tw-text-grey-muted tw-pl-2 tw-text-xs tw-font-medium tw-mt-1.5"
|
||||
data-testid="last-reply">
|
||||
Last reply{' '}
|
||||
{toLower(getDayTimeByTimeStamp(lastReplyTimeStamp as number))}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeedCardFooter;
|
@ -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(<p>Avatar</p>);
|
||||
});
|
||||
|
||||
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(<FeedCardHeader {...mockFeedHeaderProps} />, {
|
||||
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(
|
||||
<FeedCardHeader {...mockFeedHeaderProps} isEntityFeed={false} />,
|
||||
{
|
||||
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();
|
||||
});
|
||||
});
|
@ -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<FeedHeaderProp> = ({
|
||||
className,
|
||||
createdBy,
|
||||
timeStamp,
|
||||
entityFQN,
|
||||
entityType,
|
||||
entityField,
|
||||
isEntityFeed,
|
||||
}) => {
|
||||
const [userData, setUserData] = useState<User>({} 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 (
|
||||
<Fragment>
|
||||
{isError ? (
|
||||
<p>Error while getting user data.</p>
|
||||
) : (
|
||||
<div>
|
||||
{isLoading ? (
|
||||
<Loader size="small" />
|
||||
) : (
|
||||
<div>
|
||||
<div className="tw-flex">
|
||||
<div className="tw-mr-2">
|
||||
<Avatar name={createdBy} type="square" width="30" />
|
||||
</div>
|
||||
<div className="tw-self-center">
|
||||
<p>
|
||||
<span className="tw-font-medium tw-mr-2">
|
||||
{displayName}
|
||||
</span>
|
||||
<span className="tw-text-grey-muted">{name}</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="tw-text-left">
|
||||
{teams?.length || roles?.length ? (
|
||||
<hr className="tw-my-2 tw--mx-3" />
|
||||
) : null}
|
||||
{teams?.length ? (
|
||||
<p className="tw-mt-2">
|
||||
<SVGIcons
|
||||
alt="icon"
|
||||
className="tw-w-4"
|
||||
icon={Icons.TEAMS_GREY}
|
||||
/>
|
||||
<span className="tw-mr-2 tw-ml-1 tw-align-middle tw-font-medium">
|
||||
Teams
|
||||
</span>
|
||||
<span>
|
||||
{teams.map((team, i) => (
|
||||
<span
|
||||
className="tw-bg-gray-200 tw-rounded tw-px-1 tw-text-grey-body tw-m-0.5 tw-text-xs"
|
||||
key={i}>
|
||||
{team?.displayName ?? team?.name}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</p>
|
||||
) : null}
|
||||
{roles?.length ? (
|
||||
<p className="tw-mt-2">
|
||||
<SVGIcons
|
||||
alt="icon"
|
||||
className="tw-w-4"
|
||||
icon={Icons.USERS}
|
||||
/>
|
||||
<span className="tw-mr-2 tw-ml-1 tw-align-middle tw-font-medium">
|
||||
Roles
|
||||
</span>
|
||||
<span>
|
||||
{roles.map((role, i) => (
|
||||
<span
|
||||
className="tw-bg-gray-200 tw-rounded tw-px-1 tw-text-grey-body tw-m-0.5 tw-text-xs"
|
||||
key={i}>
|
||||
{role?.displayName ?? role?.name}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classNames('tw-flex tw-mb-1.5', className)}>
|
||||
<PopOver
|
||||
hideDelay={500}
|
||||
html={getUserData()}
|
||||
position="top"
|
||||
theme="light"
|
||||
trigger="mouseenter">
|
||||
<span
|
||||
className="tw-cursor-pointer"
|
||||
data-testid="authorAvatar"
|
||||
onMouseEnter={onMousEnterHandler}>
|
||||
<Avatar name={createdBy} type="square" width="30" />
|
||||
</span>
|
||||
</PopOver>
|
||||
<h6 className="tw-flex tw-items-center tw-m-0 tw-heading tw-pl-2">
|
||||
{createdBy}
|
||||
{entityFQN && entityType ? (
|
||||
<span className="tw-pl-1 tw-font-normal" data-testid="headerText">
|
||||
posted on{' '}
|
||||
{isEntityFeed ? (
|
||||
<span className="tw-heading" data-testid="headerText-entityField">
|
||||
{entityField}
|
||||
</span>
|
||||
) : (
|
||||
<Fragment>
|
||||
<span data-testid="entityType">{entityType} </span>
|
||||
<Link
|
||||
data-testid="entitylink"
|
||||
to={`${getEntityLink(
|
||||
entityType as string,
|
||||
entityFQN as string
|
||||
)}${
|
||||
entityType !== EntityType.WEBHOOK
|
||||
? `/${TabSpecificField.ACTIVITY_FEED}`
|
||||
: ''
|
||||
}`}>
|
||||
<button className="link-text" disabled={AppState.isTourOpen}>
|
||||
{getPartialNameFromFQN(
|
||||
entityFQN as string,
|
||||
entityType === 'table' ? ['table'] : ['database']
|
||||
) || entityFQN}
|
||||
</button>
|
||||
</Link>
|
||||
</Fragment>
|
||||
)}
|
||||
</span>
|
||||
) : null}
|
||||
<span
|
||||
className="tw-text-grey-muted tw-pl-2 tw-text-xs"
|
||||
data-testid="timestamp">
|
||||
{getDayTimeByTimeStamp(timeStamp)}
|
||||
</span>
|
||||
</h6>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeedCardHeader;
|
@ -50,6 +50,7 @@ const MyData: React.FC<MyDataProps> = ({
|
||||
feedFilterHandler,
|
||||
isFeedLoading,
|
||||
postFeedHandler,
|
||||
deletePostHandler,
|
||||
}: MyDataProps): React.ReactElement => {
|
||||
const [fieldListVisible, setFieldListVisible] = useState<boolean>(false);
|
||||
const isMounted = useRef(false);
|
||||
@ -177,6 +178,7 @@ const MyData: React.FC<MyDataProps> = ({
|
||||
<ActivityFeedList
|
||||
withSidePanel
|
||||
className=""
|
||||
deletePostHandler={deletePostHandler}
|
||||
feedList={feedData}
|
||||
isLoading={isFeedLoading}
|
||||
postFeedHandler={postFeedHandler}
|
||||
|
@ -36,4 +36,5 @@ export interface MyDataProps {
|
||||
entityCounts: EntityCounts;
|
||||
isFeedLoading?: boolean;
|
||||
postFeedHandler: (value: string, id: string) => void;
|
||||
deletePostHandler?: (threadId: string, postId: string) => void;
|
||||
}
|
||||
|
@ -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 ? (
|
||||
<MyData
|
||||
countServices={countServices}
|
||||
deletePostHandler={deletePostHandler}
|
||||
entityCounts={entityCounts}
|
||||
error={error}
|
||||
feedData={entityThread || []}
|
||||
|
@ -165,7 +165,9 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
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 || '',
|
||||
|
Loading…
x
Reference in New Issue
Block a user