UI: Add support for deleting a post in table entity (#3405)

* UI: Add support for deleting a post in table entity

* Addressing review comment
This commit is contained in:
Sachin Chaurasiya 2022-03-14 19:21:10 +05:30 committed by GitHub
parent 3dfae301fe
commit eb436adcb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 358 additions and 135 deletions

View File

@ -74,3 +74,10 @@ export const postFeedById: Function = (
): Promise<AxiosResponse> => { ): Promise<AxiosResponse> => {
return APIClient.post(`/feed/${id}/posts`, data); return APIClient.post(`/feed/${id}/posts`, data);
}; };
export const deletePostById: Function = (
threadId: string,
postId: string
): Promise<AxiosResponse> => {
return APIClient.delete(`/feed/${threadId}/posts/${postId}`);
};

View File

@ -0,0 +1,61 @@
/*
* 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 { Post } from 'Models';
import { HTMLAttributes } from 'react';
export interface ConfirmState {
state: boolean;
threadId: string | undefined;
postId: string | undefined;
}
export interface ActivityFeedCardProp extends HTMLAttributes<HTMLDivElement> {
feed: Post;
entityLink?: string;
repliedUsers?: Array<string>;
replies?: number;
isEntityFeed?: boolean;
threadId?: string;
lastReplyTimeStamp?: number;
isFooterVisible?: boolean;
onThreadSelect?: (id: string) => void;
deletePostHandler?: (threadId: string, postId: string) => void;
}
export interface FeedHeaderProp
extends HTMLAttributes<HTMLDivElement>,
Pick<ActivityFeedCardProp, 'isEntityFeed'> {
createdBy: string;
timeStamp: number;
entityType: string;
entityFQN: string;
entityField: string;
}
export interface FeedBodyProp
extends HTMLAttributes<HTMLDivElement>,
Pick<ActivityFeedCardProp, 'deletePostHandler'> {
message: string;
postId: string;
threadId: string;
onConfirmation: (data: ConfirmState) => void;
}
export interface FeedFooterProp
extends HTMLAttributes<HTMLDivElement>,
Pick<
ActivityFeedCardProp,
| 'replies'
| 'repliedUsers'
| 'threadId'
| 'onThreadSelect'
| 'lastReplyTimeStamp'
| 'isFooterVisible'
> {}

View File

@ -14,8 +14,7 @@
import { AxiosResponse } from 'axios'; import { AxiosResponse } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { isUndefined, toLower } from 'lodash'; import { isUndefined, toLower } from 'lodash';
import { Post } from 'Models'; import React, { FC, Fragment, useState } from 'react';
import React, { FC, Fragment, HTMLAttributes, useState } from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import AppState from '../../../AppState'; import AppState from '../../../AppState';
import { getUserByName } from '../../../axiosAPIs/userAPI'; import { getUserByName } from '../../../axiosAPIs/userAPI';
@ -36,41 +35,14 @@ import Avatar from '../../common/avatar/Avatar';
import PopOver from '../../common/popover/PopOver'; import PopOver from '../../common/popover/PopOver';
import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer'; import RichTextEditorPreviewer from '../../common/rich-text-editor/RichTextEditorPreviewer';
import Loader from '../../Loader/Loader'; import Loader from '../../Loader/Loader';
import ConfirmationModal from '../../Modals/ConfirmationModal/ConfirmationModal';
interface ActivityFeedCardProp extends HTMLAttributes<HTMLDivElement> { import {
feed: Post; ActivityFeedCardProp,
entityLink?: string; ConfirmState,
repliedUsers?: Array<string>; FeedBodyProp,
replies?: number; FeedFooterProp,
isEntityFeed?: boolean; FeedHeaderProp,
threadId?: string; } from './ActivityFeedCard.interface';
lastReplyTimeStamp?: number;
isFooterVisible?: boolean;
onThreadSelect?: (id: string) => void;
}
interface FeedHeaderProp
extends HTMLAttributes<HTMLDivElement>,
Pick<ActivityFeedCardProp, 'isEntityFeed'> {
createdBy: string;
timeStamp: number;
entityType: string;
entityFQN: string;
entityField: string;
}
interface FeedBodyProp extends HTMLAttributes<HTMLDivElement> {
message: string;
}
interface FeedFooterProp
extends HTMLAttributes<HTMLDivElement>,
Pick<
ActivityFeedCardProp,
| 'replies'
| 'repliedUsers'
| 'threadId'
| 'onThreadSelect'
| 'lastReplyTimeStamp'
| 'isFooterVisible'
> {}
const FeedHeader: FC<FeedHeaderProp> = ({ const FeedHeader: FC<FeedHeaderProp> = ({
className, className,
@ -230,15 +202,31 @@ const FeedHeader: FC<FeedHeaderProp> = ({
); );
}; };
const FeedBody: FC<FeedBodyProp> = ({ message, className }) => { const FeedBody: FC<FeedBodyProp> = ({
message,
className,
threadId,
postId,
deletePostHandler,
onConfirmation,
}) => {
return ( return (
<div className={className}> <Fragment>
<RichTextEditorPreviewer <div className={className}>
className="activity-feed-card-text" <RichTextEditorPreviewer
enableSeeMoreVariant={false} className="activity-feed-card-text"
markdown={getFrontEndFormat(message)} enableSeeMoreVariant={false}
/> markdown={getFrontEndFormat(message)}
</div> />
{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>
); );
}; };
@ -296,11 +284,37 @@ const ActivityFeedCard: FC<ActivityFeedCardProp> = ({
lastReplyTimeStamp, lastReplyTimeStamp,
onThreadSelect, onThreadSelect,
isFooterVisible = false, isFooterVisible = false,
deletePostHandler,
}) => { }) => {
const entityType = getEntityType(entityLink as string); const entityType = getEntityType(entityLink as string);
const entityFQN = getEntityFQN(entityLink as string); const entityFQN = getEntityFQN(entityLink as string);
const entityField = getEntityField(entityLink as string); const entityField = getEntityField(entityLink as string);
const [confirmationState, setConfirmationState] = useState<ConfirmState>({
state: false,
threadId: undefined,
postId: undefined,
});
const onCancel = () => {
setConfirmationState({
state: false,
threadId: undefined,
postId: undefined,
});
};
const onDelete = () => {
if (confirmationState.postId && confirmationState.threadId) {
deletePostHandler?.(confirmationState.threadId, confirmationState.postId);
}
onCancel();
};
const onConfirmation = (data: ConfirmState) => {
setConfirmationState(data);
};
return ( return (
<div className={classNames(className)}> <div className={classNames(className)}>
<FeedHeader <FeedHeader
@ -312,8 +326,12 @@ const ActivityFeedCard: FC<ActivityFeedCardProp> = ({
timeStamp={feed.postTs} timeStamp={feed.postTs}
/> />
<FeedBody <FeedBody
className="tw-mx-7 tw-ml-9 tw-bg-white tw-p-3 tw-border tw-border-main tw-rounded-md tw-break-all" 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}
message={feed.message} message={feed.message}
postId={feed.id}
threadId={threadId as string}
onConfirmation={onConfirmation}
/> />
<FeedFooter <FeedFooter
className="tw-ml-9 tw-mt-3" className="tw-ml-9 tw-mt-3"
@ -324,6 +342,18 @@ const ActivityFeedCard: FC<ActivityFeedCardProp> = ({
threadId={threadId} threadId={threadId}
onThreadSelect={onThreadSelect} onThreadSelect={onThreadSelect}
/> />
{confirmationState.state && (
<ConfirmationModal
bodyClassName="tw-h-18"
bodyText="Are you sure you want to permanently remove this post?"
cancelText="Cancel"
className="tw-w-auto"
confirmText="Delete"
header="Delete Post?"
onCancel={onCancel}
onConfirm={onDelete}
/>
)}
</div> </div>
); );
}; };

View File

@ -0,0 +1,44 @@
/*
* 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 { EntityThread } from 'Models';
import { HTMLAttributes } from 'react';
export interface ActivityFeedListProp extends HTMLAttributes<HTMLDivElement> {
feedList: EntityThread[];
withSidePanel?: boolean;
isEntityFeed?: boolean;
entityName?: string;
postFeedHandler?: (value: string, id: string) => void;
deletePostHandler?: (threadId: string, postId: string) => void;
}
export interface FeedListSeparatorProp extends HTMLAttributes<HTMLDivElement> {
relativeDay: string;
}
export interface FeedListBodyProp
extends HTMLAttributes<HTMLDivElement>,
Pick<FeedListSeparatorProp, 'relativeDay'>,
Pick<
ActivityFeedListProp,
'isEntityFeed' | 'withSidePanel' | 'deletePostHandler'
> {
updatedFeedList: Array<EntityThread & { relativeDay: string }>;
selctedThreadId: string;
onThreadIdSelect: (value: string) => void;
onThreadIdDeselect: () => void;
onThreadSelect: (value: string) => void;
postFeed: (value: string) => void;
onViewMore: () => void;
}

View File

@ -14,13 +14,7 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import { EntityThread } from 'Models'; import { EntityThread } from 'Models';
import React, { import React, { FC, Fragment, useEffect, useState } from 'react';
FC,
Fragment,
HTMLAttributes,
useEffect,
useState,
} from 'react';
import { withLoader } from '../../../hoc/withLoader'; import { withLoader } from '../../../hoc/withLoader';
import { getFeedListWithRelativeDays } from '../../../utils/FeedUtils'; import { getFeedListWithRelativeDays } from '../../../utils/FeedUtils';
import ActivityFeedCard, { import ActivityFeedCard, {
@ -29,30 +23,11 @@ import ActivityFeedCard, {
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor'; import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
import ActivityFeedPanel from '../ActivityFeedPanel/ActivityFeedPanel'; import ActivityFeedPanel from '../ActivityFeedPanel/ActivityFeedPanel';
import NoFeedPlaceholder from '../NoFeedPlaceholder/NoFeedPlaceholder'; import NoFeedPlaceholder from '../NoFeedPlaceholder/NoFeedPlaceholder';
import {
interface ActivityFeedListProp extends HTMLAttributes<HTMLDivElement> { ActivityFeedListProp,
feedList: EntityThread[]; FeedListBodyProp,
withSidePanel?: boolean; FeedListSeparatorProp,
isEntityFeed?: boolean; } from './ActivityFeedList.interface';
entityName?: string;
postFeedHandler?: (value: string, id: string) => void;
}
interface FeedListSeparatorProp extends HTMLAttributes<HTMLDivElement> {
relativeDay: string;
}
interface FeedListBodyProp
extends HTMLAttributes<HTMLDivElement>,
Pick<FeedListSeparatorProp, 'relativeDay'>,
Pick<ActivityFeedListProp, 'isEntityFeed' | 'withSidePanel'> {
updatedFeedList: Array<EntityThread & { relativeDay: string }>;
selctedThreadId: string;
onThreadIdSelect: (value: string) => void;
onThreadIdDeselect: () => void;
onThreadSelect: (value: string) => void;
postFeed: (value: string) => void;
onViewMore: () => void;
}
export const FeedListSeparator: FC<FeedListSeparatorProp> = ({ export const FeedListSeparator: FC<FeedListSeparatorProp> = ({
className, className,
@ -81,6 +56,7 @@ const FeedListBody: FC<FeedListBodyProp> = ({
postFeed, postFeed,
onViewMore, onViewMore,
selctedThreadId, selctedThreadId,
deletePostHandler,
}) => { }) => {
return ( return (
<Fragment> <Fragment>
@ -91,6 +67,7 @@ const FeedListBody: FC<FeedListBodyProp> = ({
message: feed.message, message: feed.message,
postTs: feed.threadTs, postTs: feed.threadTs,
from: feed.createdBy, from: feed.createdBy,
id: feed.id,
}; };
const postLength = feed.posts.length; const postLength = feed.posts.length;
const replies = feed.postsCount - 1; const replies = feed.postsCount - 1;
@ -128,8 +105,10 @@ const FeedListBody: FC<FeedListBodyProp> = ({
) : null} ) : null}
<ActivityFeedCard <ActivityFeedCard
className="tw-mb-6 tw-ml-9" className="tw-mb-6 tw-ml-9"
deletePostHandler={deletePostHandler}
feed={lastPost} feed={lastPost}
isEntityFeed={isEntityFeed} isEntityFeed={isEntityFeed}
threadId={feed.id}
/> />
<p <p
className="link-text tw-text-xs tw-underline tw-ml-9 tw-pl-9 tw--mt-4 tw-mb-6" className="link-text tw-text-xs tw-underline tw-ml-9 tw-pl-9 tw--mt-4 tw-mb-6"
@ -170,6 +149,7 @@ const ActivityFeedList: FC<ActivityFeedListProp> = ({
isEntityFeed = false, isEntityFeed = false,
postFeedHandler, postFeedHandler,
entityName, entityName,
deletePostHandler,
}) => { }) => {
const { updatedFeedList, relativeDays } = const { updatedFeedList, relativeDays } =
getFeedListWithRelativeDays(feedList); getFeedListWithRelativeDays(feedList);
@ -234,6 +214,7 @@ const ActivityFeedList: FC<ActivityFeedListProp> = ({
relativeDay={d} relativeDay={d}
/> />
<FeedListBody <FeedListBody
deletePostHandler={deletePostHandler}
isEntityFeed={isEntityFeed} isEntityFeed={isEntityFeed}
postFeed={postFeed} postFeed={postFeed}
relativeDay={d} relativeDay={d}
@ -251,6 +232,7 @@ const ActivityFeedList: FC<ActivityFeedListProp> = ({
{withSidePanel && selectedThread && isPanelOpen ? ( {withSidePanel && selectedThread && isPanelOpen ? (
<Fragment> <Fragment>
<ActivityFeedPanel <ActivityFeedPanel
deletePostHandler={deletePostHandler}
open={!isUndefined(selectedThread) && isPanelOpen} open={!isUndefined(selectedThread) && isPanelOpen}
postFeed={postFeed} postFeed={postFeed}
selectedThread={selectedThread} selectedThread={selectedThread}

View File

@ -0,0 +1,40 @@
/*
* 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 { EntityThread } from 'Models';
import { HTMLAttributes } from 'react';
export interface ActivityFeedPanelProp extends HTMLAttributes<HTMLDivElement> {
selectedThread: EntityThread;
open?: boolean;
onCancel: () => void;
postFeed: (value: string) => void;
deletePostHandler?: (threadId: string, postId: string) => void;
}
export interface FeedPanelHeaderProp
extends HTMLAttributes<HTMLHeadingElement>,
Pick<ActivityFeedPanelProp, 'onCancel'> {
entityField: string;
noun?: string;
onShowNewConversation?: (v: boolean) => void;
}
export interface FeedPanelOverlayProp
extends HTMLAttributes<HTMLButtonElement>,
Pick<ActivityFeedPanelProp, 'onCancel'> {}
export interface FeedPanelBodyProp
extends HTMLAttributes<HTMLDivElement>,
Pick<ActivityFeedPanelProp, 'deletePostHandler'> {
threadData: EntityThread;
isLoading: boolean;
}

View File

@ -15,13 +15,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { AxiosResponse } from 'axios'; import { AxiosResponse } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { EntityThread, Post } from 'Models'; import { EntityThread, Post } from 'Models';
import React, { import React, { FC, Fragment, useEffect, useState } from 'react';
FC,
Fragment,
HTMLAttributes,
useEffect,
useState,
} from 'react';
import { getFeedById } from '../../../axiosAPIs/feedsAPI'; import { getFeedById } from '../../../axiosAPIs/feedsAPI';
import { getEntityField, getReplyText } from '../../../utils/FeedUtils'; import { getEntityField, getReplyText } from '../../../utils/FeedUtils';
import { Button } from '../../buttons/Button/Button'; import { Button } from '../../buttons/Button/Button';
@ -29,28 +23,12 @@ import PopOver from '../../common/popover/PopOver';
import Loader from '../../Loader/Loader'; import Loader from '../../Loader/Loader';
import ActivityFeedCard from '../ActivityFeedCard/ActivityFeedCard'; import ActivityFeedCard from '../ActivityFeedCard/ActivityFeedCard';
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor'; import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
import {
interface ActivityFeedPanelProp extends HTMLAttributes<HTMLDivElement> { ActivityFeedPanelProp,
selectedThread: EntityThread; FeedPanelBodyProp,
open?: boolean; FeedPanelHeaderProp,
onCancel: () => void; FeedPanelOverlayProp,
postFeed: (value: string) => void; } from './ActivityFeedPanel.interface';
}
interface FeedPanelHeaderProp
extends HTMLAttributes<HTMLHeadingElement>,
Pick<ActivityFeedPanelProp, 'onCancel'> {
entityField: string;
noun?: string;
onShowNewConversation?: (v: boolean) => void;
}
interface FeedPanelOverlayProp
extends HTMLAttributes<HTMLButtonElement>,
Pick<ActivityFeedPanelProp, 'onCancel'> {}
interface FeedPanelBodyProp extends HTMLAttributes<HTMLDivElement> {
threadData: EntityThread;
isLoading: boolean;
}
export const FeedPanelHeader: FC<FeedPanelHeaderProp> = ({ export const FeedPanelHeader: FC<FeedPanelHeaderProp> = ({
onCancel, onCancel,
@ -118,12 +96,14 @@ const FeedPanelBody: FC<FeedPanelBodyProp> = ({
threadData, threadData,
className, className,
isLoading, isLoading,
deletePostHandler,
}) => { }) => {
const repliesLength = threadData?.posts?.length ?? 0; const repliesLength = threadData?.posts?.length ?? 0;
const mainThread = { const mainThread = {
message: threadData.message, message: threadData.message,
from: threadData.createdBy, from: threadData.createdBy,
postTs: threadData.threadTs, postTs: threadData.threadTs,
id: threadData.id,
}; };
return ( return (
@ -151,8 +131,10 @@ const FeedPanelBody: FC<FeedPanelBodyProp> = ({
<ActivityFeedCard <ActivityFeedCard
isEntityFeed isEntityFeed
className="tw-mb-3" className="tw-mb-3"
deletePostHandler={deletePostHandler}
feed={reply} feed={reply}
key={key} key={key}
threadId={threadData.id}
/> />
))} ))}
</Fragment> </Fragment>
@ -169,6 +151,7 @@ const ActivityFeedPanel: FC<ActivityFeedPanelProp> = ({
onCancel, onCancel,
className, className,
postFeed, postFeed,
deletePostHandler,
}) => { }) => {
const [threadData, setThreadData] = useState<EntityThread>(selectedThread); const [threadData, setThreadData] = useState<EntityThread>(selectedThread);
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
@ -204,6 +187,7 @@ const ActivityFeedPanel: FC<ActivityFeedPanelProp> = ({
<FeedPanelBody <FeedPanelBody
className="tw-p-4 tw-pl-8 tw-mb-3" className="tw-p-4 tw-pl-8 tw-mb-3"
deletePostHandler={deletePostHandler}
isLoading={isLoading} isLoading={isLoading}
threadData={threadData as EntityThread} threadData={threadData as EntityThread}
/> />

View File

@ -0,0 +1,42 @@
/*
* 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 { EntityThread } from 'Models';
import { HTMLAttributes } from 'react';
import { CreateThread } from '../../../generated/api/feed/createThread';
export interface ActivityThreadPanelProp
extends HTMLAttributes<HTMLDivElement> {
threadLink: string;
open?: boolean;
postFeedHandler: (value: string, id: string) => void;
onCancel: () => void;
createThread: (data: CreateThread) => void;
deletePostHandler?: (threadId: string, postId: string) => void;
}
export interface ActivityThreadListProp
extends HTMLAttributes<HTMLDivElement>,
Pick<ActivityThreadPanelProp, 'deletePostHandler'> {
threads: EntityThread[];
selectedThreadId: string;
postFeed: (value: string) => void;
onThreadIdSelect: (value: string) => void;
onThreadSelect: (value: string) => void;
}
export interface ActivityThreadProp
extends HTMLAttributes<HTMLDivElement>,
Pick<ActivityThreadPanelProp, 'deletePostHandler'> {
selectedThread: EntityThread;
postFeed: (value: string) => void;
}

View File

@ -15,16 +15,9 @@ import { AxiosResponse } from 'axios';
import classNames from 'classnames'; import classNames from 'classnames';
import { isUndefined } from 'lodash'; import { isUndefined } from 'lodash';
import { EntityThread, Post } from 'Models'; import { EntityThread, Post } from 'Models';
import React, { import React, { FC, Fragment, useEffect, useState } from 'react';
FC,
Fragment,
HTMLAttributes,
useEffect,
useState,
} from 'react';
import AppState from '../../../AppState'; import AppState from '../../../AppState';
import { getAllFeeds, getFeedById } from '../../../axiosAPIs/feedsAPI'; import { getAllFeeds, getFeedById } from '../../../axiosAPIs/feedsAPI';
import { CreateThread } from '../../../generated/api/feed/createThread';
import { import {
getEntityField, getEntityField,
getFeedListWithRelativeDays, getFeedListWithRelativeDays,
@ -39,26 +32,11 @@ import {
FeedPanelHeader, FeedPanelHeader,
FeedPanelOverlay, FeedPanelOverlay,
} from '../ActivityFeedPanel/ActivityFeedPanel'; } from '../ActivityFeedPanel/ActivityFeedPanel';
import {
interface ActivityThreadPanelProp extends HTMLAttributes<HTMLDivElement> { ActivityThreadListProp,
threadLink: string; ActivityThreadPanelProp,
open?: boolean; ActivityThreadProp,
postFeedHandler: (value: string, id: string) => void; } from './ActivityThreadPanel.interface';
onCancel: () => void;
createThread: (data: CreateThread) => void;
}
interface ActivityThreadListProp extends HTMLAttributes<HTMLDivElement> {
threads: EntityThread[];
selectedThreadId: string;
postFeed: (value: string) => void;
onThreadIdSelect: (value: string) => void;
onThreadSelect: (value: string) => void;
}
interface ActivityThreadProp extends HTMLAttributes<HTMLDivElement> {
selectedThread: EntityThread;
postFeed: (value: string) => void;
}
const ActivityThreadList: FC<ActivityThreadListProp> = ({ const ActivityThreadList: FC<ActivityThreadListProp> = ({
className, className,
@ -67,6 +45,7 @@ const ActivityThreadList: FC<ActivityThreadListProp> = ({
postFeed, postFeed,
onThreadIdSelect, onThreadIdSelect,
onThreadSelect, onThreadSelect,
deletePostHandler,
}) => { }) => {
const { updatedFeedList: updatedThreads, relativeDays } = const { updatedFeedList: updatedThreads, relativeDays } =
getFeedListWithRelativeDays(threads); getFeedListWithRelativeDays(threads);
@ -87,6 +66,7 @@ const ActivityThreadList: FC<ActivityThreadListProp> = ({
message: thread.message, message: thread.message,
postTs: thread.threadTs, postTs: thread.threadTs,
from: thread.createdBy, from: thread.createdBy,
id: thread.id,
}; };
const postLength = thread.posts.length; const postLength = thread.posts.length;
const replies = thread.postsCount - 1; const replies = thread.postsCount - 1;
@ -123,7 +103,9 @@ const ActivityThreadList: FC<ActivityThreadListProp> = ({
<ActivityFeedCard <ActivityFeedCard
isEntityFeed isEntityFeed
className="tw-mb-6 tw-ml-9" className="tw-mb-6 tw-ml-9"
deletePostHandler={deletePostHandler}
feed={lastPost} feed={lastPost}
threadId={thread.id}
/> />
<p <p
className="link-text tw-text-xs tw-underline tw-ml-9 tw-pl-9 tw--mt-4 tw-mb-6" className="link-text tw-text-xs tw-underline tw-ml-9 tw-pl-9 tw--mt-4 tw-mb-6"
@ -161,6 +143,7 @@ const ActivityThread: FC<ActivityThreadProp> = ({
className, className,
selectedThread, selectedThread,
postFeed, postFeed,
deletePostHandler,
}) => { }) => {
const [threadData, setThreadData] = useState<EntityThread>(selectedThread); const [threadData, setThreadData] = useState<EntityThread>(selectedThread);
const repliesLength = threadData?.posts?.length ?? 0; const repliesLength = threadData?.posts?.length ?? 0;
@ -168,6 +151,7 @@ const ActivityThread: FC<ActivityThreadProp> = ({
message: threadData.message, message: threadData.message,
from: threadData.createdBy, from: threadData.createdBy,
postTs: threadData.threadTs, postTs: threadData.threadTs,
id: threadData.id,
}; };
useEffect(() => { useEffect(() => {
@ -198,8 +182,10 @@ const ActivityThread: FC<ActivityThreadProp> = ({
<ActivityFeedCard <ActivityFeedCard
isEntityFeed isEntityFeed
className="tw-mb-3" className="tw-mb-3"
deletePostHandler={deletePostHandler}
feed={reply} feed={reply}
key={key} key={key}
threadId={threadData.id}
/> />
))} ))}
</Fragment> </Fragment>
@ -221,6 +207,7 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
open, open,
postFeedHandler, postFeedHandler,
createThread, createThread,
deletePostHandler,
}) => { }) => {
const [threads, setThreads] = useState<EntityThread[]>([]); const [threads, setThreads] = useState<EntityThread[]>([]);
const [selectedThread, setSelectedThread] = useState<EntityThread>(); const [selectedThread, setSelectedThread] = useState<EntityThread>();
@ -276,6 +263,13 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
}, 500); }, 500);
}; };
const onPostDelete = (threadId: string, postId: string) => {
deletePostHandler?.(threadId, postId);
setTimeout(() => {
getThreads();
}, 500);
};
useEffect(() => { useEffect(() => {
const escapeKeyHandler = (e: KeyboardEvent) => { const escapeKeyHandler = (e: KeyboardEvent) => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
@ -332,6 +326,7 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
</p> </p>
<ActivityThread <ActivityThread
className="tw-pb-6 tw-pl-5" className="tw-pb-6 tw-pl-5"
deletePostHandler={onPostDelete}
postFeed={postFeed} postFeed={postFeed}
selectedThread={selectedThread} selectedThread={selectedThread}
/> />
@ -353,6 +348,7 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
) : null} ) : null}
<ActivityThreadList <ActivityThreadList
className="tw-py-6 tw-pl-5" className="tw-py-6 tw-pl-5"
deletePostHandler={onPostDelete}
postFeed={postFeed} postFeed={postFeed}
selectedThreadId={selectedThreadId} selectedThreadId={selectedThreadId}
threads={threads} threads={threads}

View File

@ -118,6 +118,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
qualityTestFormHandler, qualityTestFormHandler,
handleSelectedColumn, handleSelectedColumn,
selectedColumn, selectedColumn,
deletePostHandler,
}: DatasetDetailsProps) => { }: DatasetDetailsProps) => {
const { isAuthDisabled } = useAuthContext(); const { isAuthDisabled } = useAuthContext();
const [isEdit, setIsEdit] = useState(false); const [isEdit, setIsEdit] = useState(false);
@ -565,6 +566,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
{threadLink ? ( {threadLink ? (
<ActivityThreadPanel <ActivityThreadPanel
createThread={createThread} createThread={createThread}
deletePostHandler={deletePostHandler}
open={Boolean(threadLink)} open={Boolean(threadLink)}
postFeedHandler={postFeedHandler} postFeedHandler={postFeedHandler}
threadLink={threadLink} threadLink={threadLink}
@ -595,6 +597,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
isEntityFeed isEntityFeed
withSidePanel withSidePanel
className="" className=""
deletePostHandler={deletePostHandler}
entityName={entityName} entityName={entityName}
feedList={entityThread} feedList={entityThread}
isLoading={isentityThreadLoading} isLoading={isentityThreadLoading}

View File

@ -108,4 +108,5 @@ export interface DatasetDetailsProps {
columnName: string, columnName: string,
testType: ColumnTestType testType: ColumnTestType
) => void; ) => void;
deletePostHandler: (threadId: string, postId: string) => void;
} }

View File

@ -109,6 +109,7 @@ const DatasetDetailsProps = {
handleRemoveColumnTest: jest.fn(), handleRemoveColumnTest: jest.fn(),
handleTestModeChange: jest.fn(), handleTestModeChange: jest.fn(),
qualityTestFormHandler: jest.fn(), qualityTestFormHandler: jest.fn(),
deletePostHandler: jest.fn(),
}; };
jest.mock('../ManageTab/ManageTab.component', () => { jest.mock('../ManageTab/ManageTab.component', () => {
return jest.fn().mockReturnValue(<p>ManageTab</p>); return jest.fn().mockReturnValue(<p>ManageTab</p>);

View File

@ -17,6 +17,7 @@ import React, { ReactNode } from 'react';
import { Button } from '../../buttons/Button/Button'; import { Button } from '../../buttons/Button/Button';
import Loader from '../../Loader/Loader'; import Loader from '../../Loader/Loader';
type Props = { type Props = {
className?: string;
loadingState?: LoadingState; loadingState?: LoadingState;
cancelText: string | ReactNode; cancelText: string | ReactNode;
confirmText: string | ReactNode; confirmText: string | ReactNode;
@ -43,9 +44,12 @@ const ConfirmationModal = ({
onConfirm, onConfirm,
onCancel, onCancel,
bodyText, bodyText,
className,
}: Props) => { }: Props) => {
return ( return (
<dialog className="tw-modal" data-testid="confirmation-modal"> <dialog
className={classNames('tw-modal', className)}
data-testid="confirmation-modal">
<div className="tw-modal-backdrop" /> <div className="tw-modal-backdrop" />
<div className="tw-modal-container tw-w-120"> <div className="tw-modal-container tw-w-120">
<div className={classNames('tw-modal-header', headerClassName)}> <div className={classNames('tw-modal-header', headerClassName)}>

View File

@ -568,6 +568,7 @@ declare module 'Models' {
message: string; message: string;
postTs: number; postTs: number;
from: string; from: string;
id: string;
} }
export interface EntityFieldThreadCount { export interface EntityFieldThreadCount {

View File

@ -27,6 +27,7 @@ import React, { FunctionComponent, useEffect, useState } from 'react';
import { useHistory, useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import AppState from '../../AppState'; import AppState from '../../AppState';
import { import {
deletePostById,
getAllFeeds, getAllFeeds,
getFeedCount, getFeedCount,
postFeedById, postFeedById,
@ -753,6 +754,30 @@ const DatasetDetailsPage: FunctionComponent = () => {
}); });
}; };
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 };
});
});
getEntityFeedCount();
showToast({
variant: 'success',
body: 'Post got deleted successfully',
});
}
})
.catch(() => {
showToast({ variant: 'error', body: 'Error while deleting post' });
});
};
useEffect(() => { useEffect(() => {
fetchTableDetail(); fetchTableDetail();
setActiveTab(getCurrentDatasetTab(tab)); setActiveTab(getCurrentDatasetTab(tab));
@ -786,6 +811,7 @@ const DatasetDetailsPage: FunctionComponent = () => {
createThread={createThread} createThread={createThread}
dataModel={tableDetails.dataModel} dataModel={tableDetails.dataModel}
datasetFQN={tableFQN} datasetFQN={tableFQN}
deletePostHandler={deletePostHandler}
deleted={deleted} deleted={deleted}
description={description} description={description}
descriptionUpdateHandler={descriptionUpdateHandler} descriptionUpdateHandler={descriptionUpdateHandler}

View File

@ -179,6 +179,7 @@ const TourPage = () => {
columnsUpdateHandler={handleCountChange} columnsUpdateHandler={handleCountChange}
createThread={handleCountChange} createThread={handleCountChange}
datasetFQN={mockDatasetData.datasetFQN} datasetFQN={mockDatasetData.datasetFQN}
deletePostHandler={handleCountChange}
description={mockDatasetData.description} description={mockDatasetData.description}
descriptionUpdateHandler={handleCountChange} descriptionUpdateHandler={handleCountChange}
entityFieldThreadCount={[]} entityFieldThreadCount={[]}