diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/Task/TaskTab/TaskTab.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/Task/TaskTab/TaskTab.component.test.tsx
deleted file mode 100644
index 693a3e2e33a..00000000000
--- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/Task/TaskTab/TaskTab.component.test.tsx
+++ /dev/null
@@ -1,289 +0,0 @@
-/*
- * Copyright 2023 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 { render, screen } from '@testing-library/react';
-import React from 'react';
-import { MemoryRouter } from 'react-router-dom';
-import { EntityType } from '../../../../enums/entity.enum';
-import { useAuth } from '../../../../hooks/authHooks';
-import {
- MOCK_TASK,
- MOCK_TASK_2,
- MOCK_TASK_3,
- TASK_COLUMNS,
- TASK_FEED,
-} from '../../../../mocks/Task.mock';
-import { mockUserData } from '../../../Settings/Users/mocks/User.mocks';
-import { TaskTab } from './TaskTab.component';
-import { TaskTabProps } from './TaskTab.interface';
-
-jest.mock('../../../../rest/feedsAPI', () => ({
- updateTask: jest.fn().mockImplementation(() => Promise.resolve()),
- updateThread: jest.fn().mockImplementation(() => Promise.resolve()),
-}));
-
-jest.mock('react-router-dom', () => ({
- Link: jest
- .fn()
- .mockImplementation(({ children }: { children: React.ReactNode }) => (
-
{children}
- )),
- useNavigate: jest.fn().mockReturnValue(jest.fn()),
-}));
-
-jest.mock('../../../ActivityFeed/ActivityFeedCardV2/ActivityFeedCardV2', () => {
- return jest.fn().mockImplementation(() => ActivityFeedCardV2
);
-});
-
-jest.mock('../../../ActivityFeed/ActivityFeedEditor/ActivityFeedEditor', () => {
- return jest.fn().mockImplementation(({ editAction }) => (
-
-
ActivityFeedEditor
- {editAction}
-
- ));
-});
-
-jest.mock('../../../common/OwnerLabel/OwnerLabel.component', () => ({
- OwnerLabel: jest.fn().mockImplementation(() => OwnerLabel
),
-}));
-
-jest.mock('../../../common/InlineEdit/InlineEdit.component', () => {
- return jest.fn().mockImplementation(() => InlineEdit
);
-});
-
-jest.mock('../../../../pages/TasksPage/shared/Assignees', () => {
- return jest.fn().mockImplementation(() => Assignees
);
-});
-
-jest.mock('../../../../pages/TasksPage/shared/DescriptionTask', () => {
- return jest.fn().mockImplementation(() => DescriptionTask
);
-});
-
-jest.mock('../../../../pages/TasksPage/shared/TagsTask', () => {
- return jest.fn().mockImplementation(() => TagsTask
);
-});
-
-jest.mock('../../../common/PopOverCard/EntityPopOverCard', () => {
- return jest.fn().mockImplementation(() => EntityPopOverCard
);
-});
-
-jest.mock(
- '../TaskTabIncidentManagerHeader/TaskTabIncidentManagerHeader.component',
- () => {
- return jest
- .fn()
- .mockImplementation(() => TaskTabIncidentManagerHeader
);
- }
-);
-
-jest.mock('../../../common/RichTextEditor/RichTextEditor', () => {
- return jest.fn().mockImplementation(() => RichTextEditor
);
-});
-
-jest.mock('../../../../utils/CommonUtils', () => ({
- getNameFromFQN: jest.fn().mockReturnValue('getNameFromFQN'),
-}));
-
-jest.mock('../../../../utils/EntityUtils', () => ({
- getEntityName: jest.fn().mockReturnValue('getEntityName'),
-}));
-
-jest.mock('../../../../utils/FeedUtils', () => ({
- getEntityFQN: jest.fn().mockReturnValue('getEntityFQN'),
-}));
-
-jest.mock('../../../../utils/TasksUtils', () => ({
- ...jest.requireActual('../../../../utils/TasksUtils'),
- fetchOptions: jest.fn().mockReturnValue('getEntityLink'),
- getTaskDetailPath: jest.fn().mockReturnValue('/'),
- isDescriptionTask: jest.fn().mockReturnValue(false),
- isTagsTask: jest.fn().mockReturnValue(true),
- generateOptions: jest.fn().mockReturnValue([]),
-}));
-
-jest.mock('../../../../utils/ToastUtils', () => ({
- showErrorToast: jest.fn(),
- showSuccessToast: jest.fn(),
-}));
-
-jest.mock('../../../../hooks/useApplicationStore', () => ({
- useApplicationStore: jest.fn(() => ({
- currentUser: mockUserData,
- })),
-}));
-
-jest.mock('../../../../rest/feedsAPI', () => ({
- updateTask: jest.fn(),
- updateThread: jest.fn(),
-}));
-
-jest.mock(
- '../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider',
- () => ({
- useActivityFeedProvider: jest.fn().mockImplementation(() => ({
- postFeed: jest.fn(),
- setActiveThread: jest.fn(),
- })),
- __esModule: true,
- default: 'ActivityFeedProvider',
- })
-);
-
-jest.mock('../../../../hooks/authHooks', () => ({
- useAuth: jest.fn().mockReturnValue({ isAdminUser: false }),
-}));
-
-const mockOnAfterClose = jest.fn();
-const mockOnUpdateEntityDetails = jest.fn();
-
-const mockProps: TaskTabProps = {
- taskThread: TASK_FEED,
- entityType: EntityType.TABLE,
- isForFeedTab: true,
- columns: TASK_COLUMNS,
- onAfterClose: mockOnAfterClose,
- onUpdateEntityDetails: mockOnUpdateEntityDetails,
-};
-
-describe('Test TaskFeedCard component', () => {
- it('Should render the component', async () => {
- render( , {
- wrapper: MemoryRouter,
- });
-
- const activityFeedCard = screen.getByTestId('task-tab');
-
- expect(activityFeedCard).toBeInTheDocument();
- });
-
- it('Should render the assignee and creator of task', async () => {
- render( , {
- wrapper: MemoryRouter,
- });
-
- expect(screen.getByText('label.assignee-plural:')).toBeInTheDocument();
- expect(screen.getByText('label.created-by:')).toBeInTheDocument();
- expect(screen.getAllByText('OwnerLabel')).toHaveLength(2);
- });
-
- it('should not render task action button to the task owner if task has reviewer', async () => {
- render( , {
- wrapper: MemoryRouter,
- });
-
- expect(screen.getByTestId('task-cta-buttons')).toHaveTextContent(
- 'label.comment'
- );
- expect(screen.getByTestId('task-cta-buttons')).not.toHaveTextContent(
- 'label.accept-suggestion'
- );
- expect(screen.getByTestId('task-cta-buttons')).not.toHaveTextContent(
- 'label.add-entity'
- );
- expect(screen.getByTestId('task-cta-buttons')).not.toHaveTextContent(
- 'label.add-suggestion'
- );
- });
-
- it('should render close button if the user is creator task', async () => {
- render(
- ,
- {
- wrapper: MemoryRouter,
- }
- );
-
- expect(screen.getByText('label.close')).toBeInTheDocument();
- });
-
- it('should not render close button if the user is not a creator of task', async () => {
- render( , {
- wrapper: MemoryRouter,
- });
-
- expect(screen.queryByText('label.close')).not.toBeInTheDocument();
- });
-
- it('should not render close button if the user is a creator and even have hasEditAccess of task', async () => {
- (useAuth as jest.Mock).mockImplementation(() => ({
- isAdminUser: true,
- }));
-
- render(
- ,
- {
- wrapper: MemoryRouter,
- }
- );
-
- expect(screen.queryByText('label.close')).not.toBeInTheDocument();
- });
-
- it('should not render close button if the user is a creator and assignee of task', async () => {
- render(
- ,
- {
- wrapper: MemoryRouter,
- }
- );
-
- expect(screen.queryByText('label.close')).not.toBeInTheDocument();
- });
-
- it('should render dropdown button with add and close tag if task created with no tags', async () => {
- render(
- ,
- {
- wrapper: MemoryRouter,
- }
- );
-
- expect(screen.getByTestId('add-close-task-dropdown')).toBeInTheDocument();
- expect(screen.getByText('label.add-entity')).toBeInTheDocument();
- expect(screen.getByText('label.comment')).toBeInTheDocument();
- });
-
- it('should render dropdown button with resolve and reject tag if task is Glossary approval', async () => {
- render(
- ,
- {
- wrapper: MemoryRouter,
- }
- );
-
- expect(
- screen.getByTestId('glossary-accept-reject-task-dropdown')
- ).toBeInTheDocument();
- expect(screen.getByText('label.approve')).toBeInTheDocument();
- expect(screen.getByText('label.comment')).toBeInTheDocument();
- });
-});
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/Task/TaskTab/TaskTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/Task/TaskTab/TaskTab.component.tsx
deleted file mode 100644
index 8335e2c5580..00000000000
--- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/Task/TaskTab/TaskTab.component.tsx
+++ /dev/null
@@ -1,1178 +0,0 @@
-/*
- * Copyright 2023 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 Icon, { DownOutlined } from '@ant-design/icons';
-import {
- Button,
- Col,
- Dropdown,
- Form,
- MenuProps,
- Row,
- Select,
- Space,
- Tooltip,
- Typography,
-} from 'antd';
-import { useForm } from 'antd/lib/form/Form';
-import { ItemType } from 'antd/lib/menu/hooks/useItems';
-import Modal from 'antd/lib/modal/Modal';
-import { AxiosError } from 'axios';
-import classNames from 'classnames';
-import { compare } from 'fast-json-patch';
-import {
- isEmpty,
- isEqual,
- isUndefined,
- last,
- startCase,
- unionBy,
-} from 'lodash';
-import { MenuInfo } from 'rc-menu/lib/interface';
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { useTranslation } from 'react-i18next';
-import { useNavigate } from 'react-router-dom';
-import { ReactComponent as EditIcon } from '../../../../assets/svg/edit-new.svg';
-import { ReactComponent as TaskCloseIcon } from '../../../../assets/svg/ic-close-task.svg';
-import { ReactComponent as TaskOpenIcon } from '../../../../assets/svg/ic-open-task.svg';
-import { ReactComponent as AddColored } from '../../../../assets/svg/plus-colored.svg';
-import {
- DE_ACTIVE_COLOR,
- PAGE_SIZE_MEDIUM,
-} from '../../../../constants/constants';
-import { TaskOperation } from '../../../../constants/Feeds.constants';
-import { TASK_TYPES } from '../../../../constants/Task.constant';
-import { usePermissionProvider } from '../../../../context/PermissionProvider/PermissionProvider';
-import { ResourceEntity } from '../../../../context/PermissionProvider/PermissionProvider.interface';
-import { EntityType } from '../../../../enums/entity.enum';
-import { TaskType } from '../../../../generated/api/feed/createThread';
-import { ResolveTask } from '../../../../generated/api/feed/resolveTask';
-import { CreateTestCaseResolutionStatus } from '../../../../generated/api/tests/createTestCaseResolutionStatus';
-import {
- TaskDetails,
- ThreadTaskStatus,
-} from '../../../../generated/entity/feed/thread';
-import { Operation } from '../../../../generated/entity/policies/policy';
-import { EntityReference } from '../../../../generated/tests/testCase';
-import {
- TestCaseFailureReasonType,
- TestCaseResolutionStatusTypes,
-} from '../../../../generated/tests/testCaseResolutionStatus';
-import { TagLabel } from '../../../../generated/type/tagLabel';
-import { useAuth } from '../../../../hooks/authHooks';
-import { useApplicationStore } from '../../../../hooks/useApplicationStore';
-import {
- FieldProp,
- FieldTypes,
-} from '../../../../interface/FormUtils.interface';
-import Assignees from '../../../../pages/TasksPage/shared/Assignees';
-import DescriptionTask from '../../../../pages/TasksPage/shared/DescriptionTask';
-import TagsTask from '../../../../pages/TasksPage/shared/TagsTask';
-import {
- Option,
- TaskAction,
- TaskActionMode,
-} from '../../../../pages/TasksPage/TasksPage.interface';
-import { updateTask, updateThread } from '../../../../rest/feedsAPI';
-import { postTestCaseIncidentStatus } from '../../../../rest/incidentManagerAPI';
-import { getUsers } from '../../../../rest/userAPI';
-import { getNameFromFQN } from '../../../../utils/CommonUtils';
-import EntityLink from '../../../../utils/EntityLink';
-import { getEntityReferenceListFromEntities } from '../../../../utils/EntityUtils';
-import { getEntityFQN } from '../../../../utils/FeedUtils';
-import { getField } from '../../../../utils/formUtils';
-import { checkPermission } from '../../../../utils/PermissionsUtils';
-import { getErrorText } from '../../../../utils/StringsUtils';
-import {
- fetchOptions,
- generateOptions,
- getTaskDetailPath,
- GLOSSARY_TASK_ACTION_LIST,
- INCIDENT_TASK_ACTION_LIST,
- isDescriptionTask,
- isTagsTask,
- TASK_ACTION_COMMON_ITEM,
- TASK_ACTION_LIST,
-} from '../../../../utils/TasksUtils';
-import { showErrorToast, showSuccessToast } from '../../../../utils/ToastUtils';
-import ActivityFeedCardV2 from '../../../ActivityFeed/ActivityFeedCardV2/ActivityFeedCardV2';
-import ActivityFeedEditor from '../../../ActivityFeed/ActivityFeedEditor/ActivityFeedEditor';
-import { useActivityFeedProvider } from '../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
-import InlineEdit from '../../../common/InlineEdit/InlineEdit.component';
-import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component';
-import EntityPopOverCard from '../../../common/PopOverCard/EntityPopOverCard';
-import { EditorContentRef } from '../../../common/RichTextEditor/RichTextEditor.interface';
-import TaskTabIncidentManagerHeader from '../TaskTabIncidentManagerHeader/TaskTabIncidentManagerHeader.component';
-import './task-tab.less';
-import { TaskTabProps } from './TaskTab.interface';
-
-export const TaskTab = ({
- taskThread,
- owners = [],
- entityType,
- hasGlossaryReviewer,
- ...rest
-}: TaskTabProps) => {
- const editorRef = useRef(null);
- const navigate = useNavigate();
- const [assigneesForm] = useForm();
- const { currentUser } = useApplicationStore();
- const updatedAssignees = Form.useWatch('assignees', assigneesForm);
- const { permissions } = usePermissionProvider();
- const { task: taskDetails } = taskThread;
-
- const entityFQN = useMemo(
- () => getEntityFQN(taskThread.about) ?? '',
- [taskThread.about]
- );
-
- const isEntityDetailsAvailable = useMemo(
- () => !isUndefined(entityFQN) && !isUndefined(entityType),
- [entityFQN, entityType]
- );
-
- const { t } = useTranslation();
- const [form] = Form.useForm();
- const { isAdminUser } = useAuth();
- const {
- postFeed,
- updateEntityThread,
- fetchUpdatedThread,
- updateTestCaseIncidentStatus,
- testCaseResolutionStatus,
- } = useActivityFeedProvider();
-
- const isTaskDescription = isDescriptionTask(taskDetails?.type as TaskType);
-
- const isTaskTags = isTagsTask(taskDetails?.type as TaskType);
-
- const showAddSuggestionButton = useMemo(() => {
- const taskType = taskDetails?.type ?? ('' as TaskType);
- const parsedSuggestion = [
- TaskType.UpdateDescription,
- TaskType.RequestDescription,
- ].includes(taskType)
- ? taskDetails?.suggestion
- : JSON.parse(taskDetails?.suggestion || '[]');
-
- return (
- [TaskType.RequestTag, TaskType.RequestDescription].includes(taskType) &&
- isEmpty(parsedSuggestion)
- );
- }, [taskDetails]);
-
- const noSuggestionTaskMenuOptions = useMemo(() => {
- let label;
-
- if (taskThread.task?.newValue) {
- label = t('label.add-suggestion');
- } else if (isTaskTags) {
- label = t('label.add-entity', {
- entity: t('label.tag-plural'),
- });
- } else {
- label = t('label.add-entity', {
- entity: t('label.description'),
- });
- }
-
- return [
- {
- label,
- key: TaskActionMode.EDIT,
- icon: AddColored,
- },
- ...TASK_ACTION_COMMON_ITEM,
- ];
- }, [isTaskTags, taskThread.task?.newValue]);
-
- const isTaskTestCaseResult =
- taskDetails?.type === TaskType.RequestTestCaseFailureResolution;
-
- const isTaskGlossaryApproval = taskDetails?.type === TaskType.RequestApproval;
-
- const latestAction = useMemo(() => {
- const resolutionStatus = last(testCaseResolutionStatus);
-
- if (isTaskTestCaseResult) {
- switch (resolutionStatus?.testCaseResolutionStatusType) {
- case TestCaseResolutionStatusTypes.Assigned:
- return INCIDENT_TASK_ACTION_LIST[1];
-
- default:
- return INCIDENT_TASK_ACTION_LIST[0];
- }
- } else if (isTaskGlossaryApproval) {
- return GLOSSARY_TASK_ACTION_LIST[0];
- } else if (showAddSuggestionButton) {
- return noSuggestionTaskMenuOptions[0];
- } else {
- return TASK_ACTION_LIST[0];
- }
- }, [
- showAddSuggestionButton,
- testCaseResolutionStatus,
- isTaskGlossaryApproval,
- isTaskTestCaseResult,
- noSuggestionTaskMenuOptions,
- ]);
-
- const [usersList, setUsersList] = useState([]);
- const [taskAction, setTaskAction] = useState(latestAction);
- const [isActionLoading, setIsActionLoading] = useState(false);
- const isTaskClosed = isEqual(taskDetails?.status, ThreadTaskStatus.Closed);
- const [showEditTaskModel, setShowEditTaskModel] = useState(false);
- const [comment, setComment] = useState('');
- const [isEditAssignee, setIsEditAssignee] = useState(false);
- const [options, setOptions] = useState([]);
- const [isAssigneeLoading, setIsAssigneeLoading] = useState(false);
- const { initialAssignees, assigneeOptions } = useMemo(() => {
- const initialAssignees = generateOptions(taskDetails?.assignees ?? []);
- const assigneeOptions = unionBy(
- [...initialAssignees, ...generateOptions(usersList)],
- 'value'
- );
-
- return { initialAssignees, assigneeOptions };
- }, [taskDetails, usersList]);
-
- const taskColumnName = useMemo(() => {
- const columnName = EntityLink.getTableColumnName(taskThread.about) ?? '';
-
- if (columnName) {
- return (
-
- {columnName} {t('label.in-lowercase')}
-
- );
- }
-
- return null;
- }, [taskThread.about]);
-
- const isOwner = owners?.some((owner) => isEqual(owner.id, currentUser?.id));
- const isCreator = isEqual(taskThread.createdBy, currentUser?.name);
-
- const checkIfUserPartOfTeam = useCallback(
- (teamId: string): boolean => {
- return Boolean(currentUser?.teams?.find((team) => teamId === team.id));
- },
- [currentUser]
- );
-
- const isAssignee = taskDetails?.assignees?.some((assignee) =>
- isEqual(assignee.id, currentUser?.id)
- );
-
- const isPartOfAssigneeTeam = taskDetails?.assignees?.some((assignee) =>
- assignee.type === 'team' ? checkIfUserPartOfTeam(assignee.id) : false
- );
-
- const getFormattedMenuOptions = (options: TaskAction[]) => {
- return options.map((item) => ({
- ...item,
- icon: ,
- }));
- };
-
- const handleTaskLinkClick = () => {
- navigate({
- pathname: getTaskDetailPath(taskThread),
- });
- };
-
- const taskLinkTitleElement = useMemo(
- () =>
- isEntityDetailsAvailable && !isUndefined(taskDetails) ? (
-
-
- {`#${taskDetails.id} `}
-
-
- {TASK_TYPES[taskDetails.type]}
-
-
- {taskColumnName}
-
-
- {getNameFromFQN(entityFQN)}
-
-
- {`(${entityType})`}
-
-
- ) : null,
- [
- isEntityDetailsAvailable,
- entityFQN,
- entityType,
- taskDetails,
- handleTaskLinkClick,
- ]
- );
-
- const updateTaskData = (data: TaskDetails | ResolveTask) => {
- if (!taskDetails?.id) {
- return;
- }
- updateTask(TaskOperation.RESOLVE, taskDetails?.id + '', data)
- .then(() => {
- showSuccessToast(t('server.task-resolved-successfully'));
- rest.onAfterClose?.();
- rest.onUpdateEntityDetails?.();
- })
- .catch((err: AxiosError) =>
- showErrorToast(getErrorText(err, t('server.unexpected-error')))
- );
- };
-
- const onGlossaryTaskResolve = (status = 'approved') => {
- const newValue = isTaskGlossaryApproval ? status : taskDetails?.suggestion;
- const data = { newValue: newValue };
- updateTaskData(data as TaskDetails);
- };
-
- const onTaskResolve = () => {
- if (!isTaskGlossaryApproval && isEmpty(taskDetails?.suggestion)) {
- showErrorToast(
- t('message.field-text-is-required', {
- fieldText: isTaskTags
- ? t('label.tag-plural')
- : t('label.description'),
- })
- );
-
- return;
- }
- if (isTaskTags) {
- const tagsData = {
- newValue: taskDetails?.suggestion || '[]',
- };
-
- updateTaskData(tagsData as TaskDetails);
- } else {
- const newValue = isTaskGlossaryApproval
- ? 'approved'
- : taskDetails?.suggestion;
- const data = { newValue: newValue };
- updateTaskData(data as TaskDetails);
- }
- };
-
- const onEditAndSuggest = ({
- description,
- updatedTags,
- testCaseFailureReason,
- testCaseFailureComment,
- }: {
- description: string;
- updatedTags: TagLabel[];
- testCaseFailureReason: TestCaseFailureReasonType;
- testCaseFailureComment: string;
- }) => {
- let data = {} as ResolveTask;
- if (isTaskTags) {
- data = {
- newValue: JSON.stringify(updatedTags) || '[]',
- };
- } else {
- if (isTaskTestCaseResult) {
- data = {
- newValue: testCaseFailureComment,
- testCaseFQN: entityFQN,
- testCaseFailureReason,
- };
- } else {
- data = { newValue: description };
- }
- }
-
- updateTaskData(data as ResolveTask);
- };
-
- /**
- *
- * @returns True if has access otherwise false
- */
- const hasEditAccess =
- isAdminUser ||
- isAssignee ||
- (!hasGlossaryReviewer && isOwner) ||
- (Boolean(isPartOfAssigneeTeam) && !isCreator);
-
- const onSave = () => {
- postFeed(comment, taskThread?.id ?? '')
- .catch(() => {
- // ignore since error is displayed in toast in the parent promise.
- // Added block for sonar code smell
- })
- .finally(() => {
- editorRef.current?.clearEditorContent();
- });
- };
-
- const handleMenuItemClick: MenuProps['onClick'] = (info) => {
- if (info.key === TaskActionMode.EDIT) {
- setShowEditTaskModel(true);
- } else if (info.key === TaskActionMode.CLOSE) {
- onTaskReject();
- } else {
- onTaskResolve();
- }
- setTaskAction(
- [
- ...TASK_ACTION_LIST,
- ...GLOSSARY_TASK_ACTION_LIST,
- ...INCIDENT_TASK_ACTION_LIST,
- ].find((action) => action.key === info.key) ?? TASK_ACTION_LIST[0]
- );
- };
-
- const onTaskReject = () => {
- if (!isTaskGlossaryApproval && isEmpty(comment)) {
- showErrorToast(t('server.task-closed-without-comment'));
-
- return;
- }
-
- const updatedComment = isTaskGlossaryApproval ? 'Rejected' : comment;
- updateTask(TaskOperation.REJECT, taskDetails?.id + '', {
- comment: updatedComment,
- } as unknown as TaskDetails)
- .then(() => {
- showSuccessToast(t('server.task-closed-successfully'));
- rest.onAfterClose?.();
- rest.onUpdateEntityDetails?.();
- })
- .catch((err: AxiosError) => showErrorToast(err));
- };
-
- const onTestCaseIncidentAssigneeUpdate = async () => {
- setIsActionLoading(true);
- const testCaseIncident: CreateTestCaseResolutionStatus = {
- testCaseResolutionStatusType: TestCaseResolutionStatusTypes.Assigned,
- testCaseReference: entityFQN,
- testCaseResolutionStatusDetails: {
- assignee: {
- id: updatedAssignees[0].value,
- name: updatedAssignees[0].name,
- displayName: updatedAssignees[0].displayName,
- type: updatedAssignees[0].type,
- },
- },
- };
- try {
- const response = await postTestCaseIncidentStatus(testCaseIncident);
- updateTestCaseIncidentStatus([...testCaseResolutionStatus, response]);
- fetchUpdatedThread(taskThread.id).finally(() => {
- setIsEditAssignee(false);
- });
- } catch (error) {
- showErrorToast(error as AxiosError);
- } finally {
- setIsActionLoading(false);
- }
- };
-
- const onTestCaseIncidentResolve = async ({
- testCaseFailureReason,
- testCaseFailureComment,
- }: {
- testCaseFailureReason: TestCaseFailureReasonType;
- testCaseFailureComment: string;
- }) => {
- setIsActionLoading(true);
- const testCaseIncident: CreateTestCaseResolutionStatus = {
- testCaseResolutionStatusType: TestCaseResolutionStatusTypes.Resolved,
- testCaseReference: entityFQN,
- testCaseResolutionStatusDetails: {
- resolvedBy: {
- id: currentUser?.id ?? '',
- name: currentUser?.name ?? '',
- type: 'user',
- },
- testCaseFailureReason,
- testCaseFailureComment,
- },
- };
- try {
- const response = await postTestCaseIncidentStatus(testCaseIncident);
- updateTestCaseIncidentStatus([...testCaseResolutionStatus, response]);
- rest.onAfterClose?.();
- setShowEditTaskModel(false);
- } catch (error) {
- showErrorToast(
- getErrorText(error as AxiosError, t('server.unexpected-error'))
- );
- } finally {
- setIsActionLoading(false);
- }
- };
-
- const handleTaskMenuClick = (info: MenuInfo) => {
- setTaskAction(
- INCIDENT_TASK_ACTION_LIST.find((action) => action.key === info.key) ??
- INCIDENT_TASK_ACTION_LIST[0]
- );
- switch (info.key) {
- case TaskActionMode.RE_ASSIGN:
- setIsEditAssignee(true);
-
- break;
- case TaskActionMode.RESOLVE:
- setShowEditTaskModel(true);
-
- break;
- }
- };
-
- const onTestCaseTaskDropdownClick = () => {
- if (taskAction.key === TaskActionMode.RESOLVE) {
- setShowEditTaskModel(true);
- } else {
- handleTaskMenuClick({ key: taskAction.key } as MenuInfo);
- }
- };
-
- const handleGlossaryTaskMenuClick = (info: MenuInfo) => {
- setTaskAction(
- GLOSSARY_TASK_ACTION_LIST.find((action) => action.key === info.key) ??
- GLOSSARY_TASK_ACTION_LIST[0]
- );
- switch (info.key) {
- case TaskActionMode.RESOLVE:
- onTaskResolve();
-
- break;
-
- case TaskActionMode.CLOSE:
- onGlossaryTaskResolve('rejected');
-
- break;
- }
- };
-
- const handleNoSuggestionMenuItemClick: MenuProps['onClick'] = (info) => {
- if (info.key === TaskActionMode.EDIT) {
- setShowEditTaskModel(true);
- } else {
- onTaskReject();
- }
- setTaskAction(
- noSuggestionTaskMenuOptions.find((action) => action.key === info.key) ??
- noSuggestionTaskMenuOptions[0]
- );
- };
-
- const onTaskDropdownClick = () => {
- if (taskAction.key === TaskActionMode.RESOLVE) {
- handleMenuItemClick({ key: taskAction.key } as MenuInfo);
- } else {
- onTaskReject();
- }
- };
-
- const onNoSuggestionTaskDropdownClick = () => {
- if (taskAction.key === TaskActionMode.EDIT) {
- handleNoSuggestionMenuItemClick({ key: taskAction.key } as MenuInfo);
- } else {
- onTaskReject();
- }
- };
-
- const renderCommentButton = useMemo(() => {
- return (
-
- {t('label.comment')}
-
- );
- }, [comment, onSave]);
-
- const approvalWorkflowActions = useMemo(() => {
- const hasApprovalAccess =
- isAssignee || (Boolean(isPartOfAssigneeTeam) && !isCreator);
-
- return (
-
-
- }
- menu={{
- items: getFormattedMenuOptions(GLOSSARY_TASK_ACTION_LIST),
- selectable: true,
- selectedKeys: [taskAction.key],
- onClick: handleGlossaryTaskMenuClick,
- }}
- overlayClassName="task-action-dropdown"
- onClick={onTaskDropdownClick}>
- {taskAction.label}
-
-
-
- {renderCommentButton}
-
- );
- }, [
- taskAction,
- isAssignee,
- isCreator,
- isPartOfAssigneeTeam,
- renderCommentButton,
- handleGlossaryTaskMenuClick,
- onTaskDropdownClick,
- ]);
-
- const testCaseResultFlow = useMemo(() => {
- const editPermission = checkPermission(
- Operation.EditAll,
- ResourceEntity.TEST_CASE,
- permissions
- );
- const hasApprovalAccess = isAssignee || isCreator || editPermission;
-
- return (
-
- }
- loading={isActionLoading}
- menu={{
- items: INCIDENT_TASK_ACTION_LIST as ItemType[],
- selectable: true,
- selectedKeys: [taskAction.key],
- onClick: handleTaskMenuClick,
- disabled: !hasApprovalAccess,
- }}
- onClick={onTestCaseTaskDropdownClick}>
- {taskAction.label}
-
- {renderCommentButton}
-
- );
- }, [
- taskDetails,
- isAssignee,
- isPartOfAssigneeTeam,
- taskAction,
- renderCommentButton,
- ]);
-
- const actionButtons = useMemo(() => {
- if (isTaskGlossaryApproval) {
- return approvalWorkflowActions;
- }
-
- if (isTaskTestCaseResult) {
- return testCaseResultFlow;
- }
-
- return (
-
- {isCreator && !hasEditAccess && (
-
- {t('label.close')}
-
- )}
- {hasEditAccess && (
- <>
- {showAddSuggestionButton ? (
-
- }
- menu={{
- items: getFormattedMenuOptions(noSuggestionTaskMenuOptions),
- selectable: true,
- selectedKeys: [taskAction.key],
- onClick: handleNoSuggestionMenuItemClick,
- }}
- overlayClassName="task-action-dropdown"
- onClick={onNoSuggestionTaskDropdownClick}>
- {taskAction.label}
-
-
- ) : (
- }
- menu={{
- items: getFormattedMenuOptions(TASK_ACTION_LIST),
- selectable: true,
- selectedKeys: [taskAction.key],
- onClick: handleMenuItemClick,
- }}
- overlayClassName="task-action-dropdown"
- onClick={() =>
- handleMenuItemClick({ key: taskAction.key } as MenuInfo)
- }>
- {taskAction.label}
-
- )}
- >
- )}
- {renderCommentButton}
-
- );
- }, [
- onTaskReject,
- taskDetails,
- onTaskResolve,
- handleMenuItemClick,
- taskAction,
- isTaskClosed,
- isTaskGlossaryApproval,
- showAddSuggestionButton,
- isCreator,
- approvalWorkflowActions,
- testCaseResultFlow,
- isTaskTestCaseResult,
- renderCommentButton,
- handleNoSuggestionMenuItemClick,
- onNoSuggestionTaskDropdownClick,
- ]);
-
- 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]);
-
- const handleAssigneeUpdate = async () => {
- setIsAssigneeLoading(true);
- const updatedTaskThread = {
- ...taskThread,
- task: {
- ...taskThread.task,
- assignees: updatedAssignees.map((assignee: Option) => ({
- id: assignee.value,
- type: assignee.type,
- })),
- },
- };
- try {
- const patch = compare(taskThread, updatedTaskThread);
- const data = await updateThread(taskThread.id, patch);
- setIsEditAssignee(false);
- updateEntityThread(data);
- } catch (error) {
- showErrorToast(error as AxiosError);
- } finally {
- setIsAssigneeLoading(false);
- }
- };
-
- const fetchInitialAssign = useCallback(async () => {
- try {
- const { data } = await getUsers({
- limit: PAGE_SIZE_MEDIUM,
-
- isBot: false,
- });
- const filterData = getEntityReferenceListFromEntities(
- data,
- EntityType.USER
- );
- setUsersList(filterData);
- } catch (error) {
- setUsersList([]);
- }
- }, []);
-
- useEffect(() => {
- // fetch users only when the task is a test case result and the assignees are getting edited
- if (isTaskTestCaseResult && isEmpty(usersList) && isEditAssignee) {
- fetchInitialAssign();
- }
- }, [isTaskTestCaseResult, usersList, isEditAssignee]);
-
- useEffect(() => {
- assigneesForm.setFieldValue('assignees', initialAssignees);
- setOptions(assigneeOptions);
- }, [initialAssignees, assigneeOptions]);
-
- useEffect(() => {
- setTaskAction(latestAction);
- }, [latestAction]);
-
- const taskHeader = isTaskTestCaseResult ? (
-
- ) : (
-
-
- {isEditAssignee ? (
-
- {
- setIsEditAssignee(false);
- assigneesForm.setFieldValue('assignees', initialAssignees);
- }}
- onSave={() => assigneesForm.submit()}>
- 0}
- options={options}
- value={updatedAssignees}
- onChange={(values) =>
- assigneesForm.setFieldValue('assignees', values)
- }
- onSearch={(query) =>
- fetchOptions({
- query,
- setOptions,
- currentUserId: currentUser?.id,
- initialOptions: assigneeOptions,
- })
- }
- />
-
-
-
- ) : (
- <>
-
- {t('label.assignee-plural')}:{' '}
-
-
- {(isCreator || hasEditAccess) &&
- !isTaskClosed &&
- owners.length === 0 ? (
-
}
- size="small"
- type="text"
- onClick={() => setIsEditAssignee(true)}
- />
- ) : null}
- >
- )}
-
-
-
- {t('label.created-by')}:{' '}
-
-
-
-
- );
-
- const descriptionField: FieldProp = useMemo(
- () => ({
- name: 'testCaseFailureComment',
- required: true,
- label: t('label.comment'),
- id: 'root/description',
- type: FieldTypes.DESCRIPTION,
- rules: [
- {
- required: true,
- message: t('label.field-required', {
- field: t('label.comment'),
- }),
- },
- ],
- props: {
- 'data-testid': 'description',
- initialValue: '',
- placeHolder: t('message.write-your-text', {
- text: t('label.comment'),
- }),
- },
- }),
- []
- );
-
- return (
-
-
-
-
- {taskLinkTitleElement}
-
- {taskHeader}
-
- {isTaskDescription && (
- form.setFieldValue('description', value)}
- />
- )}
-
- {isTaskTags && (
- form.setFieldValue('updatedTags', value)}
- />
- )}
-
-
- {taskThread?.posts?.map((reply) => (
-
- ))}
-
-
-
-
- {taskDetails?.status === ThreadTaskStatus.Open && (
-
- )}
-
- {isTaskTestCaseResult ? (
- setShowEditTaskModel(false)}
- onOk={form.submit}>
-
-
- {Object.values(TestCaseFailureReasonType).map((value) => (
- {startCase(value)}
- ))}
-
-
- {getField(descriptionField)}
-
-
- ) : (
- {
- form.resetFields();
- setShowEditTaskModel(false);
- }}
- onOk={form.submit}>
-
- form.setFieldValue('updatedTags', value)}
- />
-
- ) : (
-
- form.setFieldValue('description', value)}
- />
-
- )}
-
-
- )}
- {isTaskTestCaseResult && (
- setIsEditAssignee(false)}
- onOk={assigneesForm.submit}>
-
-
- assigneesForm.setFieldValue('assignees', values)
- }
- onSearch={(query) =>
- fetchOptions({
- query,
- setOptions,
- initialOptions: assigneeOptions,
- })
- }
- />
-
-
-
- )}
-
- );
-};