refactor(ui): task flow (#12248)

* refactor(ui): task flow

* fix: task workflow

* fix(ui): pass initial value on modal form based on task type

* fix(ui): pass proper value in initial form values.

---------

Co-authored-by: karanh37 <karanh37@gmail.com>
This commit is contained in:
Sachin Chaurasiya 2023-07-05 18:30:15 +05:30 committed by GitHub
parent ba3a1e1ed6
commit 6139c59a87
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 87 additions and 137 deletions

View File

@ -56,8 +56,6 @@ import { ReactComponent as TaskIcon } from '/assets/svg/ic-task.svg';
export const ActivityFeedTab = ({
fqn,
owner,
tags,
description,
columns,
entityType,
onUpdateEntityDetails,
@ -385,20 +383,16 @@ export const ActivityFeedTab = ({
{entityType === EntityType.TABLE ? (
<TaskTab
columns={columns}
description={description}
entityType={EntityType.TABLE}
owner={owner}
tags={tags}
task={selectedThread}
taskThread={selectedThread}
onUpdateEntityDetails={onUpdateEntityDetails}
/>
) : (
<TaskTab
description={description}
entityType={isUserEntity ? entityTypeTask : entityType}
owner={owner}
tags={tags}
task={selectedThread}
taskThread={selectedThread}
onUpdateEntityDetails={onUpdateEntityDetails}
/>
)}

View File

@ -13,7 +13,6 @@
import { EntityType } from 'enums/entity.enum';
import { Column } from 'generated/entity/data/table';
import { EntityReference } from 'generated/entity/type';
import { TagLabel } from 'generated/type/tagLabel';
export type FeedKeys = 'all' | 'mentions' | 'tasks';
@ -28,8 +27,6 @@ export interface ActivityFeedTabBasicProps {
onFeedUpdate: () => void;
onUpdateEntityDetails?: () => void;
owner?: EntityReference;
tags?: TagLabel[];
description?: string;
}
export type ActivityFeedTabProps = ActivityFeedTabBasicProps &

View File

@ -29,7 +29,6 @@ import ActivityFeedEditor from 'components/ActivityFeed/ActivityFeedEditor/Activ
import { useActivityFeedProvider } from 'components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
import { OwnerLabel } from 'components/common/OwnerLabel/OwnerLabel.component';
import EntityPopOverCard from 'components/common/PopOverCard/EntityPopOverCard';
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
import { TaskOperation } from 'constants/Feeds.constants';
import { TaskType } from 'generated/api/feed/createThread';
import { TaskDetails, ThreadTaskStatus } from 'generated/entity/feed/thread';
@ -43,16 +42,14 @@ import {
TaskActionMode,
} from 'pages/TasksPage/TasksPage.interface';
import { MenuInfo } from 'rc-menu/lib/interface';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Link, useHistory } from 'react-router-dom';
import { updateTask } from 'rest/feedsAPI';
import { getNameFromFQN } from 'utils/CommonUtils';
import { ENTITY_LINK_SEPARATOR } from 'utils/EntityUtils';
import { getEntityField, getEntityFQN, prepareFeedLink } from 'utils/FeedUtils';
import { getEntityFQN, prepareFeedLink } from 'utils/FeedUtils';
import { getEntityLink } from 'utils/TableUtils';
import {
getColumnObject,
isDescriptionTask,
isTagsTask,
TASK_ACTION_LIST,
@ -63,15 +60,13 @@ import { ReactComponent as TaskCloseIcon } from '/assets/svg/ic-close-task.svg';
import { ReactComponent as TaskOpenIcon } from '/assets/svg/ic-open-task.svg';
export const TaskTab = ({
task,
taskThread,
owner,
entityType,
tags,
description,
...rest
}: TaskTabProps) => {
const { task: taskDetails } = task;
const entityFQN = getEntityFQN(task.about) ?? '';
const { task: taskDetails } = taskThread;
const entityFQN = getEntityFQN(taskThread.about) ?? '';
const entityCheck = !isUndefined(entityFQN) && !isUndefined(entityType);
const { t } = useTranslation();
const [form] = Form.useForm();
@ -90,27 +85,8 @@ export const TaskTab = ({
[AppState.userDetails, AppState.nonSecureUserDetails]
);
const entityField = useMemo(() => {
return getEntityField(task.about);
}, [task]);
const columnObject = useMemo(() => {
// prepare column from entityField
const column = entityField?.split(ENTITY_LINK_SEPARATOR)?.slice(-2)?.[0];
// prepare column value by replacing double quotes
const columnValue = column?.replaceAll(/^"|"$/g, '') || '';
/**
* Get column name by spliting columnValue with FQN Separator
*/
const columnName = columnValue.split(FQN_SEPARATOR_CHAR).pop();
return getColumnObject(columnName ?? '', rest.columns || []);
}, [task, rest.columns]);
const isOwner = isEqual(owner?.id, currentUser?.id);
const isCreator = isEqual(task.createdBy, currentUser?.name);
const isCreator = isEqual(taskThread.createdBy, currentUser?.name);
const checkIfUserPartOfTeam = useCallback(
(teamId: string): boolean => {
@ -216,35 +192,13 @@ export const TaskTab = ({
const hasTaskUpdateAccess = () => hasEditAccess() || isPartOfAssigneeTeam;
// prepare current tags for update tags task
const getCurrentTags = () => {
if (!isEmpty(columnObject) && entityField) {
return columnObject.tags ?? [];
} else {
return tags ?? [];
}
};
// prepare current description for update description task
const currentDescription = () => {
if (entityField && !isEmpty(columnObject)) {
return columnObject.description || '';
} else {
return description || '';
}
};
const onSave = (message: string) => {
postFeed(message, task?.id ?? '').catch(() => {
postFeed(message, taskThread?.id ?? '').catch(() => {
// ignore since error is displayed in toast in the parent promise.
// Added block for sonar code smell
});
};
useEffect(() => {
form.setFieldValue('description', currentDescription());
}, [columnObject, entityField, currentDescription]);
const handleMenuItemClick: MenuProps['onClick'] = (info) => {
if (info.key === TaskActionMode.EDIT) {
setShowEditTaskModel(true);
@ -276,19 +230,26 @@ export const TaskTab = ({
return null;
}
const parsedSuggestion = [
'RequestDescription',
'UpdateDescription',
].includes(taskDetails?.type ?? '')
? taskDetails?.suggestion
: JSON.parse(taskDetails?.suggestion || '[]');
return (
<Space
className="m-t-sm items-end w-full"
data-testid="task-cta-buttons"
size="small">
{isCreator && (
{(isCreator || hasTaskUpdateAccess()) && (
<Button onClick={onTaskReject}>{t('label.close')}</Button>
)}
{hasTaskUpdateAccess() ? (
<>
{['RequestDescription', 'RequestTag'].includes(
taskDetails?.type ?? ''
) && isEmpty(taskDetails?.suggestion) ? (
) && isEmpty(parsedSuggestion) ? (
<Button
type="primary"
onClick={() =>
@ -328,6 +289,21 @@ export const TaskTab = ({
isCreator,
]);
const initialFormValue = useMemo(() => {
if (isTaskDescription) {
const description =
taskDetails?.suggestion ?? taskDetails?.oldValue ?? '';
return { description };
} else {
const updatedTags = JSON.parse(
taskDetails?.suggestion ?? taskDetails?.oldValue ?? '[]'
);
return { updatedTags };
}
}, [taskDetails, isTaskDescription]);
return (
<Row className="p-y-sm p-x-md" gutter={[0, 24]}>
<Col className="d-flex items-center" span={24}>
@ -362,7 +338,7 @@ export const TaskTab = ({
</Typography.Text>
<OwnerLabel
hasPermission={false}
owner={{ name: task.createdBy, type: 'user', id: '' }}
owner={{ name: taskThread.createdBy, type: 'user', id: '' }}
onUpdate={noop}
/>
</div>
@ -373,35 +349,32 @@ export const TaskTab = ({
<DescriptionTask
hasEditAccess={hasEditAccess()}
isTaskActionEdit={false}
suggestion={task.task?.suggestion ?? ''}
taskDetail={task}
value={currentDescription()}
taskThread={taskThread}
onChange={(value) => form.setFieldValue('description', value)}
/>
)}
{isTaskTags && (
<TagsTask
currentTags={getCurrentTags()}
hasEditAccess={hasEditAccess()}
isTaskActionEdit={false}
task={taskDetails}
value={JSON.parse(taskDetails?.suggestion ?? '[]')}
onChange={(value) => form.setFieldValue('updatedTags', value)}
/>
)}
<div className="m-l-lg">
{task?.posts?.map((reply) => (
{taskThread?.posts?.map((reply) => (
<ActivityFeedCardV1
isPost
feed={task}
feed={taskThread}
hidePopover={false}
key={reply.id}
post={reply}
/>
))}
</div>
{task.task?.status === ThreadTaskStatus.Open && (
{taskDetails?.status === ThreadTaskStatus.Open && (
<ActivityFeedEditor onSave={onSave} onTextChange={setComment} />
)}
@ -414,16 +387,20 @@ export const TaskTab = ({
open={showEditTaskModel}
title={`${t('label.edit-entity', {
entity: t('label.task-lowercase'),
})} #${taskDetails?.id} ${task.message}`}
})} #${taskDetails?.id} ${taskThread.message}`}
width={768}
onCancel={() => setShowEditTaskModel(false)}
onOk={form.submit}>
<Form form={form} layout="vertical" onFinish={onEditAndSuggest}>
<Form
form={form}
initialValues={initialFormValue}
layout="vertical"
onFinish={onEditAndSuggest}>
{isTaskTags ? (
<Form.Item
data-testid="tags-label"
label={t('label.tag-plural')}
name="updateTags"
name="updatedTags"
rules={[
{
required: true,
@ -431,12 +408,13 @@ export const TaskTab = ({
fieldText: t('label.tag-plural'),
}),
},
]}>
]}
trigger="onChange">
<TagsTask
isTaskActionEdit
currentTags={getCurrentTags()}
hasEditAccess={hasEditAccess()}
task={taskDetails}
onChange={(value) => form.setFieldValue('updatedTags', value)}
/>
</Form.Item>
) : (
@ -452,13 +430,11 @@ export const TaskTab = ({
}),
},
]}
valuePropName="suggestion">
trigger="onTextChange">
<DescriptionTask
isTaskActionEdit
hasEditAccess={hasEditAccess()}
suggestion={task.task?.suggestion ?? ''}
taskDetail={task}
value={currentDescription()}
taskThread={taskThread}
onChange={(value) => form.setFieldValue('description', value)}
/>
</Form.Item>

View File

@ -14,14 +14,11 @@ import { EntityType } from 'enums/entity.enum';
import { Column } from 'generated/entity/data/table';
import { Thread } from 'generated/entity/feed/thread';
import { EntityReference } from 'generated/entity/type';
import { TagLabel } from 'generated/type/tagLabel';
export type TaskTabProps = {
task: Thread;
taskThread: Thread;
owner?: EntityReference;
tags?: TagLabel[];
onUpdateEntityDetails?: () => void;
description?: string;
} & (
| TableTaskTabProps
| { columns?: undefined; entityType: Exclude<EntityType, EntityType.TABLE> }

View File

@ -556,11 +556,9 @@ const TableDetailsPageV1 = () => {
<ActivityFeedProvider>
<ActivityFeedTab
columns={tableDetails?.columns}
description={tableDetails?.description}
entityType={EntityType.TABLE}
fqn={tableDetails?.fullyQualifiedName ?? ''}
owner={tableDetails?.owner}
tags={tableDetails?.tags}
onFeedUpdate={getEntityFeedCount}
onUpdateEntityDetails={fetchTableDetails}
/>

View File

@ -171,7 +171,7 @@ const UpdateTag = () => {
}
form.setFieldsValue({
title: message.trimEnd(),
updateTags: getTags(),
updatedTags: getTags(),
assignees: defaultAssignee,
});
}, [entityData]);
@ -244,7 +244,7 @@ const UpdateTag = () => {
label={t('label.update-entity', {
entity: t('label.tag-plural'),
})}
name="updateTags"
name="updatedTags"
rules={[
{
required: true,

View File

@ -21,7 +21,7 @@ import {
import DescriptionTask from './DescriptionTask';
const mockProps = {
taskDetail: {
taskThread: {
id: '9542599e-f2f9-46d1-9fc0-d03620351a0d',
type: 'Task',
href: 'http://localhost:8585/api/v1/feed/9542599e-f2f9-46d1-9fc0-d03620351a0d',
@ -84,8 +84,8 @@ describe('Test Description Task Component', () => {
render(
<DescriptionTask
{...mockProps}
taskDetail={{
...mockProps.taskDetail,
taskThread={{
...mockProps.taskThread,
task: {
id: 5,
assignees: [
@ -125,8 +125,8 @@ describe('Test Description Task Component', () => {
render(
<DescriptionTask
{...mockProps}
taskDetail={{
...mockProps.taskDetail,
taskThread={{
...mockProps.taskThread,
task: {
id: 5,
assignees: [

View File

@ -25,42 +25,30 @@ import { DescriptionTabs } from './DescriptionTabs';
import { DiffView } from './DiffView';
interface DescriptionTaskProps {
taskDetail: Thread;
taskThread: Thread;
isTaskActionEdit: boolean;
hasEditAccess: boolean;
suggestion: string;
value: string;
onChange: (value: string) => void;
}
const DescriptionTask: FC<DescriptionTaskProps> = ({
taskDetail,
taskThread,
isTaskActionEdit,
hasEditAccess,
suggestion,
value: currentDescription = '',
onChange,
}) => {
const { task } = taskThread;
const { t } = useTranslation();
const isRequestDescription = isEqual(
taskDetail.task?.type,
TaskType.RequestDescription
);
const isRequestDescription = isEqual(task?.type, TaskType.RequestDescription);
const isUpdateDescription = isEqual(
taskDetail.task?.type,
TaskType.UpdateDescription
);
const isUpdateDescription = isEqual(task?.type, TaskType.UpdateDescription);
const isTaskClosed = isEqual(
taskDetail.task?.status,
ThreadTaskStatus.Closed
);
const isTaskClosed = isEqual(task?.status, ThreadTaskStatus.Closed);
const getDiffView = () => {
const oldValue = taskDetail.task?.oldValue;
const newValue = taskDetail.task?.newValue;
const oldValue = task?.oldValue;
const newValue = task?.newValue;
if (!oldValue && !newValue) {
return (
<div className="tw-border tw-border-main tw-p-2 tw-rounded tw-my-1 tw-mb-3">
@ -73,10 +61,7 @@ const DescriptionTask: FC<DescriptionTaskProps> = ({
return (
<DiffView
className="tw-border tw-border-main tw-p-2 tw-rounded tw-my-1 tw-mb-3"
diffArr={getDescriptionDiff(
taskDetail?.task?.oldValue || '',
taskDetail?.task?.newValue || ''
)}
diffArr={getDescriptionDiff(oldValue ?? '', newValue ?? '')}
/>
);
}
@ -87,8 +72,8 @@ const DescriptionTask: FC<DescriptionTaskProps> = ({
* @returns Suggested description diff
*/
const getSuggestedDescriptionDiff = () => {
const newDescription = taskDetail?.task?.suggestion;
const oldDescription = taskDetail?.task?.oldValue;
const newDescription = task?.suggestion;
const oldDescription = task?.oldValue;
const diffs = getDescriptionDiff(
oldDescription || '',
@ -116,7 +101,7 @@ const DescriptionTask: FC<DescriptionTaskProps> = ({
{isTaskActionEdit && hasEditAccess ? (
<RichTextEditor
height="208px"
initialValue={suggestion}
initialValue={task?.suggestion ?? ''}
placeHolder={t('label.add-entity', {
entity: t('label.description'),
})}
@ -135,8 +120,8 @@ const DescriptionTask: FC<DescriptionTaskProps> = ({
<div data-testid="update-description">
{isTaskActionEdit && hasEditAccess ? (
<DescriptionTabs
suggestion={suggestion}
value={currentDescription}
suggestion={task?.suggestion ?? ''}
value={task?.oldValue ?? ''}
onChange={onChange}
/>
) : (

View File

@ -48,7 +48,7 @@ describe('Test Description Tabs Component', () => {
expect(await screen.findByText('New')).toBeInTheDocument();
});
it('Should render the component relavant tab component', async () => {
it('Should render the component relevant tab component', async () => {
render(<TagsTabs {...mockProps} />);
const tabs = await screen.findAllByRole('tab');

View File

@ -12,6 +12,7 @@
*/
import { diffArrays } from 'diff';
import { TagLabel } from 'generated/type/tagLabel';
import React, { FC, Fragment, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import {
@ -19,7 +20,6 @@ import {
Thread,
ThreadTaskStatus,
} from '../../../generated/entity/feed/thread';
import { TagLabel } from '../../../generated/type/tagLabel';
import { TagsDiffView } from './TagsDiffView';
import { TagsTabs } from './TagsTabs';
import TagSuggestion from './TagSuggestion';
@ -28,18 +28,14 @@ interface TagsTaskProps {
task: Thread['task'];
isTaskActionEdit: boolean;
hasEditAccess: boolean;
currentTags: TagLabel[];
value?: TagLabel[];
onChange?: (newTags: TagLabel[]) => void;
onChange: (newTags: TagLabel[]) => void;
}
const TagsTask: FC<TagsTaskProps> = ({
value = [],
onChange,
isTaskActionEdit,
hasEditAccess,
task,
currentTags,
onChange,
}) => {
const { t } = useTranslation();
@ -105,7 +101,10 @@ const TagsTask: FC<TagsTaskProps> = ({
{isRequestTag && (
<div data-testid="request-tags">
{isTaskActionEdit && hasEditAccess ? (
<TagSuggestion value={value} onChange={onChange} />
<TagSuggestion
value={JSON.parse(suggestion ?? '[]')}
onChange={onChange}
/>
) : (
suggestedTagsDiff
)}
@ -115,8 +114,8 @@ const TagsTask: FC<TagsTaskProps> = ({
<div data-testid="update-tags">
{isTaskActionEdit && hasEditAccess ? (
<TagsTabs
tags={currentTags}
value={value}
tags={JSON.parse(oldValue ?? '[]')}
value={JSON.parse(suggestion ?? '[]')}
onChange={onChange}
/>
) : (

View File

@ -168,6 +168,10 @@ a[href].link-text-grey,
white-space: normal;
}
.whitespace-pre-wrap {
white-space: pre-wrap;
}
.mx-auto {
margin-right: auto;
margin-left: auto;