diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx index cb08bb400c8..4983b1771f7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx @@ -466,6 +466,10 @@ const DashboardDetails = ({ = ({ = ({ void; + entityFieldTasks?: EntityFieldThreads[]; + onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void; followHandler?: () => void; tagsHandler?: (selectedTags?: Array) => void; versionHandler?: () => void; @@ -88,8 +94,11 @@ const EntityPageInfo = ({ onThreadLinkSelect, entityFqn, entityType, + entityFieldTasks, }: Props) => { + const history = useHistory(); const tagThread = entityFieldThreads?.[0]; + const tagTask = entityFieldTasks?.[0]; const [isEditable, setIsEditable] = useState(false); const [entityFollowers, setEntityFollowers] = useState>(followersList); @@ -101,6 +110,10 @@ const EntityPageInfo = ({ document.getElementById('version-and-follow-section')?.offsetWidth ); + const handleRequestTags = () => { + history.push(getRequestTagsPath(entityType as string, entityFqn as string)); + }; + const handleTagSelection = (selectedTags?: Array) => { if (selectedTags) { const prevTags = @@ -269,35 +282,74 @@ const EntityPageInfo = ({ const getThreadElements = () => { if (!isUndefined(entityFieldThreads)) { - return !isUndefined(tagThread) ? ( -

onThreadLinkSelect?.(tagThread.entityLink)}> - + {tagThread.count} -

+ ) : ( -

onThreadLinkSelect?.( getEntityFeedLink(entityType, entityFqn, 'tags') ) }> - -

+ + ); } else { return null; } }; + const getRequestTagsElements = useCallback(() => { + const hasTags = !isEmpty(tags); + + return onThreadLinkSelect && !hasTags ? ( + + ) : null; + }, [tags]); + + const getTaskElement = useCallback(() => { + return !isUndefined(tagTask) ? ( + + ) : null; + }, [tagTask]); + useEffect(() => { setEntityFollowers(followersList); }, [followersList]); @@ -454,7 +506,7 @@ const EntityPageInfo = ({ position="bottom" trigger="click">
{ // Fetch tags and terms only once @@ -479,14 +531,9 @@ const EntityPageInfo = ({ }}> {tags.length || tier ? ( ) : ( @@ -501,7 +548,11 @@ const EntityPageInfo = ({
- {getThreadElements()} +
+ {getRequestTagsElements()} + {getTaskElement()} + {getThreadElements()} +
)} diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts index 3f029329883..39ea8f0eb0c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts +++ b/openmetadata-ui/src/main/resources/ui/src/constants/constants.ts @@ -209,7 +209,9 @@ export const ROUTES = { // Tasks Routes REQUEST_DESCRIPTION: `/request-description/${PLACEHOLDER_ROUTE_ENTITY_TYPE}/${PLACEHOLDER_ROUTE_ENTITY_FQN}`, + REQUEST_TAGS: `/request-tags/${PLACEHOLDER_ROUTE_ENTITY_TYPE}/${PLACEHOLDER_ROUTE_ENTITY_FQN}`, UPDATE_DESCRIPTION: `/update-description/${PLACEHOLDER_ROUTE_ENTITY_TYPE}/${PLACEHOLDER_ROUTE_ENTITY_FQN}`, + UPDATE_TAGS: `/update-tags/${PLACEHOLDER_ROUTE_ENTITY_TYPE}/${PLACEHOLDER_ROUTE_ENTITY_FQN}`, TASK_DETAIL: `/tasks/${PLACEHOLDER_TASK_ID}`, ACTIVITY_PUSH_FEED: '/api/v1/push/feed', diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/RequestTagPage/RequestTagPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/RequestTagPage/RequestTagPage.tsx new file mode 100644 index 00000000000..64a98beffe2 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/RequestTagPage/RequestTagPage.tsx @@ -0,0 +1,282 @@ +/* + * 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 { Button, Card, Input } from 'antd'; +import { AxiosError, AxiosResponse } from 'axios'; +import { capitalize, isNil } from 'lodash'; +import { observer } from 'mobx-react'; +import { EntityTags } from 'Models'; +import React, { + ChangeEvent, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; +import { useHistory, useLocation, useParams } from 'react-router-dom'; +import AppState from '../../../AppState'; +import { postThread } from '../../../axiosAPIs/feedsAPI'; +import ProfilePicture from '../../../components/common/ProfilePicture/ProfilePicture'; +import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component'; +import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants'; +import { EntityField } from '../../../constants/feed.constants'; +import { EntityType } from '../../../enums/entity.enum'; +import { + CreateThread, + TaskType, +} from '../../../generated/api/feed/createThread'; +import { ThreadType } from '../../../generated/entity/feed/thread'; +import { TagLabel } from '../../../generated/type/tagLabel'; +import { getEntityName } from '../../../utils/CommonUtils'; +import { + ENTITY_LINK_SEPARATOR, + getEntityFeedLink, +} from '../../../utils/EntityUtils'; +import { getTagsWithoutTier, getTierTags } from '../../../utils/TableUtils'; +import { + fetchEntityDetail, + fetchOptions, + getBreadCrumbList, + getColumnObject, + getTaskDetailPath, +} from '../../../utils/TasksUtils'; +import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; +import Assignees from '../shared/Assignees'; +import TagSuggestion from '../shared/TagSuggestion'; +import TaskPageLayout from '../shared/TaskPageLayout'; +import { cardStyles } from '../TaskPage.styles'; +import { EntityData, Option } from '../TasksPage.interface'; + +const RequestTag = () => { + const location = useLocation(); + const history = useHistory(); + + const { entityType, entityFQN } = useParams<{ [key: string]: string }>(); + const queryParams = new URLSearchParams(location.search); + + const field = queryParams.get('field'); + const value = queryParams.get('value'); + + const [entityData, setEntityData] = useState({} as EntityData); + const [options, setOptions] = useState([]); + const [assignees, setAssignees] = useState([]); + const [title, setTitle] = useState(''); + const [suggestion, setSuggestion] = useState([]); + + const entityTier = useMemo(() => { + const tierFQN = getTierTags(entityData.tags || [])?.tagFQN; + + return tierFQN?.split(FQN_SEPARATOR_CHAR)[1]; + }, [entityData.tags]); + + const entityTags = useMemo(() => { + const tags: EntityTags[] = getTagsWithoutTier(entityData.tags || []) || []; + + return tags.map((tag) => `#${tag.tagFQN}`).join(' '); + }, [entityData.tags]); + + const getSanitizeValue = value?.replaceAll(/^"|"$/g, '') || ''; + + const message = `Request tags for ${getSanitizeValue || entityType}`; + + // get current user details + const currentUser = useMemo( + () => AppState.getCurrentUserDetails(), + [AppState.userDetails, AppState.nonSecureUserDetails] + ); + + const back = () => history.goBack(); + + const getColumnDetails = useCallback(() => { + if (!isNil(field) && !isNil(value) && field === EntityField.COLUMNS) { + const column = getSanitizeValue.split(FQN_SEPARATOR_CHAR).slice(-1); + + const columnObject = getColumnObject(column[0], entityData.columns || []); + + return ( +
+

Column Details

+

+ Type:{' '} + {columnObject.dataTypeDisplay} +

+

{columnObject?.tags?.map((tag) => `#${tag.tagFQN}`)?.join(' ')}

+
+ ); + } else { + return null; + } + }, [entityData.columns]); + + const onSearch = (query: string) => { + fetchOptions(query, setOptions); + }; + + const getTaskAbout = () => { + if (field && value) { + return `${field}${ENTITY_LINK_SEPARATOR}${value}${ENTITY_LINK_SEPARATOR}tags`; + } else { + return EntityField.TAGS; + } + }; + + const onTitleChange = (e: ChangeEvent) => { + const { value: newValue } = e.target; + setTitle(newValue); + }; + + const onCreateTask = () => { + if (assignees.length) { + const data: CreateThread = { + from: currentUser?.name as string, + message: title || message, + about: getEntityFeedLink(entityType, entityFQN, getTaskAbout()), + taskDetails: { + assignees: assignees.map((assignee) => ({ + id: assignee.value, + type: assignee.type, + })), + suggestion: JSON.stringify(suggestion), + type: TaskType.RequestTag, + oldValue: '[]', + }, + type: ThreadType.Task, + }; + postThread(data) + .then((res: AxiosResponse) => { + showSuccessToast('Task Created Successfully'); + history.push(getTaskDetailPath(res.data.task.id)); + }) + .catch((err: AxiosError) => showErrorToast(err)); + } else { + showErrorToast('Cannot create a task without assignee'); + } + }; + + useEffect(() => { + fetchEntityDetail( + entityType as EntityType, + entityFQN as string, + setEntityData + ); + }, [entityFQN, entityType]); + + useEffect(() => { + const owner = entityData.owner; + if (owner) { + const defaultAssignee = [ + { + label: getEntityName(owner), + value: owner.id || '', + type: owner.type, + }, + ]; + setAssignees(defaultAssignee); + setOptions(defaultAssignee); + } + setTitle(message); + }, [entityData]); + + return ( + + +
+ +
+ Title:{' '} + +
+ +
+ Assignees:{' '} + +
+ +

+ Suggest tags:{' '} +

+ + +
+ + +
+
+ +
+
{capitalize(entityType)} Details
+
+ Owner:{' '} + + {entityData.owner ? ( + + + + {getEntityName(entityData.owner)} + + + ) : ( + No Owner + )} + +
+ +

+ {entityTier ? ( + entityTier + ) : ( + No Tier + )} +

+ +

{entityTags}

+ + {getColumnDetails()} +
+
+
+ ); +}; + +export default observer(RequestTag); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/TaskDetailPage/TaskDetailPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/TaskDetailPage/TaskDetailPage.tsx index 646ebd64b05..426fa74df8b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/TaskDetailPage/TaskDetailPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/TaskDetailPage/TaskDetailPage.tsx @@ -13,14 +13,14 @@ import { faChevronDown } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Button, Card, Dropdown, Layout, Menu, Modal, Tabs } from 'antd'; +import { Button, Card, Dropdown, Layout, Menu, Tabs } from 'antd'; import { AxiosError, AxiosResponse } from 'axios'; import classNames from 'classnames'; import { compare, Operation } from 'fast-json-patch'; -import { isEmpty, isEqual, isUndefined, toLower } from 'lodash'; +import { isEmpty, isEqual, toLower } from 'lodash'; import { observer } from 'mobx-react'; -import { EditorContentRef, EntityReference, EntityTags } from 'Models'; -import React, { Fragment, useEffect, useMemo, useRef, useState } from 'react'; +import { EntityReference } from 'Models'; +import React, { Fragment, useEffect, useMemo, useState } from 'react'; import { useHistory, useParams } from 'react-router-dom'; import AppState from '../../../AppState'; import { useAuthContext } from '../../../authentication/auth-provider/AuthProvider'; @@ -37,17 +37,14 @@ import ActivityFeedEditor from '../../../components/ActivityFeed/ActivityFeedEdi import FeedPanelBody from '../../../components/ActivityFeed/ActivityFeedPanel/FeedPanelBody'; import ActivityThreadPanelBody from '../../../components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanelBody'; import AssigneeList from '../../../components/common/AssigneeList/AssigneeList'; -import Ellipses from '../../../components/common/Ellipses/Ellipses'; import ErrorPlaceHolder from '../../../components/common/error-with-placeholder/ErrorPlaceHolder'; import UserPopOverCard from '../../../components/common/PopOverCard/UserPopOverCard'; import ProfilePicture from '../../../components/common/ProfilePicture/ProfilePicture'; -import RichTextEditor from '../../../components/common/rich-text-editor/RichTextEditor'; import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component'; import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants'; -import { TaskOperation } from '../../../constants/feed.constants'; +import { PanelTab, TaskOperation } from '../../../constants/feed.constants'; import { EntityType } from '../../../enums/entity.enum'; import { CreateThread } from '../../../generated/api/feed/createThread'; -import { Column } from '../../../generated/entity/data/table'; import { TaskDetails, TaskType, @@ -55,8 +52,8 @@ import { ThreadTaskStatus, ThreadType, } from '../../../generated/entity/feed/thread'; +import { TagLabel } from '../../../generated/type/tagLabel'; import { useAuth } from '../../../hooks/authHooks'; -import { getEntityName } from '../../../utils/CommonUtils'; import { ENTITY_LINK_SEPARATOR } from '../../../utils/EntityUtils'; import { deletePost, @@ -67,24 +64,26 @@ import { } from '../../../utils/FeedUtils'; import { getEncodedFqn } from '../../../utils/StringsUtils'; import SVGIcons from '../../../utils/SvgUtils'; -import { - getEntityLink, - getTagsWithoutTier, - getTierTags, -} from '../../../utils/TableUtils'; +import { getEntityLink } from '../../../utils/TableUtils'; import { fetchEntityDetail, fetchOptions, getBreadCrumbList, getColumnObject, - getDescriptionDiff, + isDescriptionTask, + isTagsTask, TASK_ACTION_LIST, } from '../../../utils/TasksUtils'; import { getDayTimeByTimeStamp } from '../../../utils/TimeUtils'; import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils'; import Assignees from '../shared/Assignees'; -import { DescriptionTabs } from '../shared/DescriptionTabs'; -import { DiffView } from '../shared/DiffView'; +import ClosedTask from '../shared/ClosedTask'; +import ColumnDetail from '../shared/ColumnDetail'; +import CommentModal from '../shared/CommentModal'; +import DescriptionTask from '../shared/DescriptionTask'; +import EntityDetail from '../shared/EntityDetail'; +import TagsTask from '../shared/TagsTask'; +import TaskStatus from '../shared/TaskStatus'; import { background, cardStyles, contentStyles } from '../TaskPage.styles'; import { EntityData, @@ -102,8 +101,6 @@ const TaskDetailPage = () => { const { taskId } = useParams<{ [key: string]: string }>(); - const markdownRef = useRef(); - const [taskDetail, setTaskDetail] = useState({} as Thread); const [taskFeedDetail, setTaskFeedDetail] = useState({} as Thread); const [entityData, setEntityData] = useState({} as EntityData); @@ -116,6 +113,7 @@ const TaskDetailPage = () => { const [taskAction, setTaskAction] = useState(TASK_ACTION_LIST[0]); const [modalVisible, setModalVisible] = useState(false); const [comment, setComment] = useState(''); + const [tagsSuggestion, setTagsSuggestion] = useState([]); // get current user details const currentUser = useMemo( @@ -123,18 +121,6 @@ const TaskDetailPage = () => { [AppState.userDetails, AppState.nonSecureUserDetails] ); - const entityTier = useMemo(() => { - const tierFQN = getTierTags(entityData.tags || [])?.tagFQN; - - return tierFQN?.split(FQN_SEPARATOR_CHAR)[1]; - }, [entityData.tags]); - - const entityTags = useMemo(() => { - const tags: EntityTags[] = getTagsWithoutTier(entityData.tags || []) || []; - - return tags.map((tag) => `#${tag.tagFQN}`).join(' '); - }, [entityData.tags]); - const entityType = useMemo(() => { return getEntityType(taskDetail.about); }, [taskDetail]); @@ -144,24 +130,25 @@ const TaskDetailPage = () => { }, [taskDetail]); 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 as string, entityData.columns || []); }, [taskDetail, entityData]); - const isRequestDescription = isEqual( - taskDetail.task?.type, - TaskType.RequestDescription - ); - - const isUpdateDescription = isEqual( - taskDetail.task?.type, - TaskType.UpdateDescription - ); + // const isRequestTag = isEqual(taskDetail.task?.type, TaskType.RequestTag); + // const isUpdateTag = isEqual(taskDetail.task?.type, TaskType.UpdateTag); const isOwner = isEqual(entityData.owner?.id, currentUser?.id); + const isAssignee = taskDetail.task?.assignees?.some((assignee) => isEqual(assignee.id, currentUser?.id) ); @@ -175,6 +162,12 @@ const TaskDetailPage = () => { const isTaskActionEdit = isEqual(taskAction.key, TaskActionMode.EDIT); + const isTaskDescription = isDescriptionTask( + taskDetail.task?.type as TaskType + ); + + const isTaskTags = isTagsTask(taskDetail.task?.type as TaskType); + const fetchTaskDetail = () => { getTask(taskId) .then((res: AxiosResponse) => { @@ -305,8 +298,7 @@ const TaskDetailPage = () => { }; const onTaskResolve = () => { - if (suggestion) { - const data = { newValue: suggestion }; + const updateTaskData = (data: Record) => { updateTask(TaskOperation.RESOLVE, taskDetail.task?.id, data) .then(() => { showSuccessToast('Task Resolved Successfully'); @@ -318,10 +310,24 @@ const TaskDetailPage = () => { ); }) .catch((err: AxiosError) => showErrorToast(err)); + }; + + if (isTaskTags) { + if (!isEmpty(tagsSuggestion)) { + const data = { newValue: JSON.stringify(tagsSuggestion || '[]') }; + updateTaskData(data); + } else { + showErrorToast('Cannot accept an empty tag list. Please add a tags.'); + } } else { - showErrorToast( - 'Cannot accept an empty description. Please add a description.' - ); + if (suggestion) { + const data = { newValue: suggestion }; + updateTaskData(data); + } else { + showErrorToast( + 'Cannot accept an empty description. Please add a description.' + ); + } } }; @@ -381,6 +387,7 @@ const TaskDetailPage = () => { updateThreadData(threadId, postId, isThread, data, callback); }; + // prepare current description for update description task const currentDescription = () => { if (entityField && !isEmpty(columnObject)) { return columnObject.description || ''; @@ -389,24 +396,47 @@ const TaskDetailPage = () => { } }; + // handle assignees search const onSearch = (query: string) => { fetchOptions(query, setOptions); }; + // handle sider tab change const onTabChange = (key: string) => { - if (isEqual(key, '1')) { + if (isEqual(key, PanelTab.TASKS)) { fetchTaskFeed(taskDetail.id); } }; + // handle task action change const onTaskActionChange = (key: string) => { setTaskAction( TASK_ACTION_LIST.find((action) => isEqual(action.key, key)) as TaskAction ); }; + /** + * + * @param taskSuggestion suggestion value + * Based on task type set's the suggestion for task + */ + const setTaskSuggestionOnRender = (taskSuggestion: string | undefined) => { + if (isTaskTags) { + const tagsSuggestion = JSON.parse(taskSuggestion || '[]'); + isEmpty(tagsSuggestion) && setTaskAction(TASK_ACTION_LIST[1]); + setTagsSuggestion(tagsSuggestion); + } else { + if (!taskSuggestion) { + setTaskAction(TASK_ACTION_LIST[1]); + } + setSuggestion(taskSuggestion || ''); + } + }; + + // handle task details change const onTaskDetailChange = () => { if (!isEmpty(taskDetail)) { + // get entityFQN and fetch entity data const entityFQN = getEntityFQN(taskDetail.about); entityFQN && @@ -417,8 +447,8 @@ const TaskDetailPage = () => { ); fetchTaskFeed(taskDetail.id); + // set task assignees const taskAssignees = taskDetail.task?.assignees || []; - const taskSuggestion = taskDetail.task?.suggestion; if (taskAssignees.length) { const assigneesArr = taskAssignees.map((assignee) => ({ label: assignee.name as string, @@ -428,10 +458,9 @@ const TaskDetailPage = () => { setAssignees(assigneesArr); setOptions(assigneesArr); } - if (!taskSuggestion) { - setTaskAction(TASK_ACTION_LIST[1]); - } - setSuggestion(taskSuggestion || ''); + + // set task suggestion on render + setTaskSuggestionOnRender(taskDetail.task?.suggestion); } }; @@ -447,154 +476,18 @@ const TaskDetailPage = () => { onTaskDetailChange(); }, [taskDetail]); - const TaskStatusElement = ({ status }: { status: ThreadTaskStatus }) => { - const openCheck = isEqual(status, ThreadTaskStatus.Open); - const closedCheck = isEqual(status, ThreadTaskStatus.Closed); - - return ( - -
- - - {status} - -
-
- ); - }; - - const ColumnDetail = ({ column }: { column: Column }) => { - return !isEmpty(column) && !isUndefined(column) ? ( -
-
- - Column type: - {' '} - - {column.dataTypeDisplay} - -
- {column.tags && column.tags.length ? ( -
- -
{column.tags.map((tag) => `#${tag.tagFQN}`)?.join(' ')}
-
- ) : null} -
- ) : null; - }; - - const EntityDetail = () => { - return ( -
-
- Owner:{' '} - - {entityData.owner ? ( - - - - {getEntityName(entityData.owner)} - - - ) : ( - No Owner - )} - - | -

- {entityTier ? ( - entityTier - ) : ( - No Tier - )} -

-
-

- {entityTags} -

-
- ); - }; - - const getDiffView = () => { - const oldValue = taskDetail.task?.oldValue; - const newValue = taskDetail.task?.newValue; - if (!oldValue && !newValue) { - return ( -
- No Description -
- ); - } else { - return ( - - ); - } - }; - - const getCurrentDescription = () => { - const newDescription = taskDetail?.task?.suggestion; - const oldDescription = taskDetail?.task?.oldValue; - - const diffs = getDescriptionDiff( - oldDescription || '', - newDescription || '' - ); - - return !newDescription && !oldDescription ? ( - No Suggestion - ) : ( - - ); - }; - - const onModalClose = () => { + // handle comment modal close + const onCommentModalClose = () => { setModalVisible(false); setComment(''); }; - const hasEditAccess = () => { - return isAdminUser || isAuthDisabled || isAssignee || isOwner; - }; + /** + * + * @returns True if has access otherwise false + */ + const hasEditAccess = () => + isAdminUser || isAuthDisabled || isAssignee || isOwner; return ( @@ -613,7 +506,7 @@ const TaskDetailPage = () => { }, ]} /> - + { data-testid="task-title"> {`Task #${taskId}`} {taskDetail.message}

-

- + @@ -651,7 +544,7 @@ const TaskDetailPage = () => { )} -

+
@@ -718,54 +611,24 @@ const TaskDetailPage = () => { -
-

Description:

{' '} - {!isEmpty(taskDetail) && ( - - {isTaskClosed ? ( - getDiffView() - ) : ( -
- {isRequestDescription && ( -
- {isTaskActionEdit && hasEditAccess() ? ( - - ) : ( -
- {getCurrentDescription()} -
- )} -
- )} - - {isUpdateDescription && ( -
- {isTaskActionEdit && hasEditAccess() ? ( - - ) : ( -
- {getCurrentDescription()} -
- )} -
- )} -
- )} -
- )} -
+ {isTaskDescription && ( + + )} + {isTaskTags && ( + + )} {hasEditAccess() && !isTaskClosed && (
{
)} - {isTaskClosed && ( -
- - - - - {taskDetail?.task?.closedBy} - {' '} - - - closed this task - - {toLower( - getDayTimeByTimeStamp( - taskDetail?.task?.closedAt as number - ) - )} - -
- )} + {isTaskClosed && }
- - - + { data-testid="task-right-sider" width={600}> - + {!isEmpty(taskFeedDetail) ? (
{ ) : null} - + {!isEmpty(taskFeedDetail) ? ( = ({ assignees, onSearch, onChange, options }) => { return ( + {options.map((d) => ( + + ))} + + ); +}; + +export default TagSuggestion; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TagsTask.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TagsTask.tsx new file mode 100644 index 00000000000..5406c7a4e68 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TagsTask.tsx @@ -0,0 +1,47 @@ +/* + * 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 { Tag } from 'antd'; +import { uniqueId } from 'lodash'; +import React, { FC } from 'react'; +import { TagLabel } from '../../../generated/type/tagLabel'; +import TagSuggestion from './TagSuggestion'; + +interface TagsTaskProps { + isTaskActionEdit: boolean; + suggestions: TagLabel[]; + setSuggestion: (value: TagLabel[]) => void; +} + +const TagsTask: FC = ({ + suggestions, + setSuggestion, + isTaskActionEdit, +}) => { + return ( +
+

Tags:

{' '} + {isTaskActionEdit ? ( + + ) : ( +
+ {suggestions.map((suggestion) => ( + {suggestion.tagFQN} + ))} +
+ )} +
+ ); +}; + +export default TagsTask; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TaskStatus.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TaskStatus.tsx new file mode 100644 index 00000000000..910f1f165dd --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/shared/TaskStatus.tsx @@ -0,0 +1,57 @@ +/* + * Copyright 2021 Collate + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import classNames from 'classnames'; +import { isEqual } from 'lodash'; +import React, { Fragment } from 'react'; +import { ThreadTaskStatus } from '../../../generated/entity/feed/thread'; + +const TaskStatus = ({ status }: { status: ThreadTaskStatus }) => { + const openCheck = isEqual(status, ThreadTaskStatus.Open); + const closedCheck = isEqual(status, ThreadTaskStatus.Closed); + + return ( + +
+ + + {status} + +
+
+ ); +}; + +export default TaskStatus; diff --git a/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx index c63d43f0a93..c6037a0648c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx @@ -155,6 +155,10 @@ const RequestDescriptionPage = withSuspenseFallback( ) ); +const RequestTagsPage = withSuspenseFallback( + React.lazy(() => import('../pages/TasksPage/RequestTagPage/RequestTagPage')) +); + const UpdateDescriptionPage = withSuspenseFallback( React.lazy( () => @@ -332,6 +336,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { /> + diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/x-master.css b/openmetadata-ui/src/main/resources/ui/src/styles/x-master.css index 82e85c9b53f..033c549c41b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/x-master.css +++ b/openmetadata-ui/src/main/resources/ui/src/styles/x-master.css @@ -1183,12 +1183,12 @@ code { } /* Antd custom button CSS */ -.ant-select-assignee { +.ant-select-custom { width: 100%; margin: 8px 0px; outline: none; } -.ant-select-assignee:not(.ant-select-disabled):hover .ant-select-selector { +.ant-select-custom:not(.ant-select-disabled):hover .ant-select-selector { border-color: #7147e8; } .ant-select-focused { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts index c08e354287e..2ef8d3e05eb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts @@ -31,6 +31,7 @@ import { import { EntityType, FqnPart } from '../enums/entity.enum'; import { ServiceCategory } from '../enums/service.enum'; import { Column, Table } from '../generated/entity/data/table'; +import { TaskType } from '../generated/entity/feed/thread'; import { EntityReference } from '../generated/type/entityReference'; import { EntityData, @@ -65,6 +66,26 @@ export const getRequestDescriptionPath = ( return { pathname, search: searchParams.toString() }; }; +export const getRequestTagsPath = ( + entityType: string, + entityFQN: string, + field?: string, + value?: string +) => { + let pathname = ROUTES.REQUEST_TAGS; + pathname = pathname + .replace(PLACEHOLDER_ROUTE_ENTITY_TYPE, entityType) + .replace(PLACEHOLDER_ROUTE_ENTITY_FQN, entityFQN); + const searchParams = new URLSearchParams(); + + if (!isUndefined(field) && !isUndefined(value)) { + searchParams.append('field', field); + searchParams.append('value', value); + } + + return { pathname, search: searchParams.toString() }; +}; + export const getUpdateDescriptionPath = ( entityType: string, entityFQN: string, @@ -263,3 +284,9 @@ export const TASK_ACTION_LIST = [ key: TaskActionMode.EDIT, }, ]; + +export const isDescriptionTask = (taskType: TaskType) => + [TaskType.RequestDescription, TaskType.UpdateDescription].includes(taskType); + +export const isTagsTask = (taskType: TaskType) => + [TaskType.RequestTag, TaskType.UpdateTag].includes(taskType);