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) ? ( - - - - ) : 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 ( - - ); - }, [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 && ( - - )} - {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 ? ( -
-
- - {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}> -
- - - - {getField(descriptionField)} -
-
- ) : ( - { - form.resetFields(); - setShowEditTaskModel(false); - }} - onOk={form.submit}> -
- {isTaskTags ? ( - - 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, - }) - } - /> - -
-
- )} -
- ); -};