supported task assignee with owner name in task thread (#17435)

* supported task assigne with owner name in task thread

* fix the mis-allignment due to conversation thread present

* fix the cypress test and assignee not updating in the task after editing
This commit is contained in:
Ashish Gupta 2024-08-14 18:47:34 +05:30 committed by GitHub
parent 455e66bc37
commit def0fcc5eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 166 additions and 131 deletions

View File

@ -19,7 +19,9 @@ import {
const owner = 'admin'; const owner = 'admin';
const assignee = 'adam.matthews2'; const assignee = 'adam.matthews2';
const assigneeDisplayName = 'Adam Matthews';
const secondAssignee = 'aaron_johnson0'; const secondAssignee = 'aaron_johnson0';
const secondAssigneeDisplayName = 'Aaron Johnson';
export type TaskDetails = { export type TaskDetails = {
assignee?: string; assignee?: string;
@ -44,9 +46,14 @@ export const verifyTaskDetails = (
expect(matches).to.not.be.null; expect(matches).to.not.be.null;
}); });
// creator of the task
cy.get('[data-testid="owner-link"]').should('contain', owner); cy.get('[data-testid="owner-link"]').should('contain', owner);
cy.get(`[data-testid="${taskAssignee ?? assignee}"]`).should('be.visible'); // assignee of the task
cy.get('[data-testid="task-assignees"] [data-testid="owner-link"]').should(
'contain',
taskAssignee ?? assigneeDisplayName
);
}; };
export const editAssignee = () => { export const editAssignee = () => {
@ -71,7 +78,14 @@ export const editAssignee = () => {
verifyResponseStatusCode('@editAssignee', 200); verifyResponseStatusCode('@editAssignee', 200);
cy.get(`[data-testid="${assignee}"]`).should('be.visible'); cy.get('[data-testid="task-assignees"] [data-testid="owner-link"]').should(
'contain',
assigneeDisplayName
);
cy.get('[data-testid="task-assignees"] [data-testid="owner-link"]').should(
'contain',
secondAssigneeDisplayName
);
}; };
export const createDescriptionTask = ( export const createDescriptionTask = (

View File

@ -369,9 +369,10 @@ describe('Task flow should work', { tags: 'DataAssets' }, () => {
verifyResponseStatusCode('@taskFeed', 200); verifyResponseStatusCode('@taskFeed', 200);
// verify the task details // verify the task details
verifyTaskDetails(/#(\d+) Request to update description for/, USER_NAME); verifyTaskDetails(
/#(\d+) Request to update description for/,
cy.get(`[data-testid="${USER_NAME}"]`).should('be.visible'); USER_DETAILS.firstName
);
// Accept the description suggestion which is created // Accept the description suggestion which is created
cy.get('.ant-btn-compact-first-item').contains('Accept Suggestion').click(); cy.get('.ant-btn-compact-first-item').contains('Accept Suggestion').click();

View File

@ -245,6 +245,21 @@ const ActivityFeedProvider = ({ children, user }: Props) => {
setEntityThread([...threads]); setEntityThread([...threads]);
}, []); }, []);
const updateEntityThread = useCallback(
(thread: Thread) => {
setEntityThread((prev) => {
return prev.map((threadItem) => {
if (threadItem.id === thread.id) {
return thread;
} else {
return threadItem;
}
});
});
},
[setEntityThread]
);
const deleteFeed = useCallback( const deleteFeed = useCallback(
async (threadId: string, postId: string, isThread: boolean) => { async (threadId: string, postId: string, isThread: boolean) => {
if (isThread) { if (isThread) {
@ -457,6 +472,7 @@ const ActivityFeedProvider = ({ children, user }: Props) => {
hideDrawer, hideDrawer,
updateEditorFocus, updateEditorFocus,
setActiveThread, setActiveThread,
updateEntityThread,
entityPaging, entityPaging,
userId: user ?? currentUser?.id ?? '', userId: user ?? currentUser?.id ?? '',
testCaseResolutionStatus, testCaseResolutionStatus,
@ -481,6 +497,7 @@ const ActivityFeedProvider = ({ children, user }: Props) => {
hideDrawer, hideDrawer,
updateEditorFocus, updateEditorFocus,
setActiveThread, setActiveThread,
updateEntityThread,
entityPaging, entityPaging,
user, user,
currentUser, currentUser,

View File

@ -34,6 +34,7 @@ export interface ActivityFeedProviderContextType {
focusReplyEditor: boolean; focusReplyEditor: boolean;
entityPaging: Paging; entityPaging: Paging;
setActiveThread: (thread?: Thread) => void; setActiveThread: (thread?: Thread) => void;
updateEntityThread: (thread: Thread) => void;
userId: string; userId: string;
deleteFeed: ( deleteFeed: (
threadId: string, threadId: string,

View File

@ -11,6 +11,11 @@
* limitations under the License. * limitations under the License.
*/ */
import {
TaskType,
ThreadTaskStatus,
} from '../../../generated/entity/feed/thread';
export const mockThreadData = [ export const mockThreadData = [
{ {
id: '465b2dfb-300e-45f5-a1a6-e19c6225e9e7', id: '465b2dfb-300e-45f5-a1a6-e19c6225e9e7',
@ -26,6 +31,32 @@ export const mockThreadData = [
postsCount: 0, postsCount: 0,
posts: [], posts: [],
relativeDay: 'Today', relativeDay: 'Today',
task: {
id: 153,
type: TaskType.RequestTag,
assignees: [
{
id: 'ef1b9e8a-75ba-4c12-9874-034c135be925',
type: 'team',
name: 'Sales',
fullyQualifiedName: 'Sales',
displayName: 'Sales',
deleted: false,
},
{
id: '83bd6004-9237-47a1-b917-ef006b3192d7',
type: 'user',
name: 'aaron_johnson0',
fullyQualifiedName: 'aaron_johnson0',
displayName: 'Aaron Johnson',
deleted: false,
},
],
status: ThreadTaskStatus.Open,
oldValue: '[]',
suggestion:
'[{"tagFQN":"PII.None","source":"Classification","name":"None","description":"Non PII"}]',
},
}, },
{ {
id: '40c2faec-0159-4d86-9b15-c17f3e1c081b', id: '40c2faec-0159-4d86-9b15-c17f3e1c081b',

View File

@ -11,7 +11,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { findByTestId, queryByTestId, render } from '@testing-library/react'; import {
findByTestId,
getByText,
queryByTestId,
render,
} from '@testing-library/react';
import React from 'react'; import React from 'react';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { mockThreadData } from './ActivityThread.mock'; import { mockThreadData } from './ActivityThread.mock';
@ -50,6 +55,10 @@ jest.mock('../ActivityFeedCard/FeedCardFooter/FeedCardFooter', () => {
return jest.fn().mockReturnValue(<p>FeedCardFooter</p>); return jest.fn().mockReturnValue(<p>FeedCardFooter</p>);
}); });
jest.mock('../../common/OwnerLabel/OwnerLabel.component', () => ({
OwnerLabel: jest.fn().mockReturnValue(<div>OwnerLabel</div>),
}));
describe('Test ActivityThreadList Component', () => { describe('Test ActivityThreadList Component', () => {
it('Check if it has all child elements', async () => { it('Check if it has all child elements', async () => {
const { container } = render( const { container } = render(
@ -145,4 +154,16 @@ describe('Test ActivityThreadList Component', () => {
expect(thread2MainMessage).toBeInTheDocument(); expect(thread2MainMessage).toBeInTheDocument();
expect(thread2QuickReplyEditor).toBeInTheDocument(); expect(thread2QuickReplyEditor).toBeInTheDocument();
}); });
it('should render task assignee when there is task', async () => {
const { container } = render(
<ActivityThreadList {...mockActivityThreadListProp} />,
{
wrapper: MemoryRouter,
}
);
expect(getByText(container, 'label.assignee-plural:')).toBeInTheDocument();
expect(getByText(container, 'OwnerLabel')).toBeInTheDocument();
});
}); });

View File

@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import { Card, Space } from 'antd'; import { Card, Typography } from 'antd';
import { isEqual } from 'lodash'; import { isEqual } from 'lodash';
import React, { FC, Fragment } from 'react'; import React, { FC, Fragment } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -29,7 +29,7 @@ import {
} from '../../../generated/entity/feed/thread'; } from '../../../generated/entity/feed/thread';
import { getFeedListWithRelativeDays } from '../../../utils/FeedUtils'; import { getFeedListWithRelativeDays } from '../../../utils/FeedUtils';
import { getTaskDetailPath } from '../../../utils/TasksUtils'; import { getTaskDetailPath } from '../../../utils/TasksUtils';
import AssigneeList from '../../common/AssigneeList/AssigneeList'; import { OwnerLabel } from '../../common/OwnerLabel/OwnerLabel.component';
import ActivityFeedCard from '../ActivityFeedCard/ActivityFeedCard'; import ActivityFeedCard from '../ActivityFeedCard/ActivityFeedCard';
import FeedCardFooter from '../ActivityFeedCard/FeedCardFooter/FeedCardFooter'; import FeedCardFooter from '../ActivityFeedCard/FeedCardFooter/FeedCardFooter';
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor'; import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
@ -166,14 +166,12 @@ const ActivityThreadList: FC<ActivityThreadListProp> = ({
</div> </div>
) : null} ) : null}
{thread.task && ( {thread.task && (
<Space wrap className="m-y-xs" size={4}> <div className="d-flex m-y-xs gap-2">
<span className="text-grey-muted"> <Typography.Text className="text-grey-muted">
{t('label.assignee-plural')}:{' '} {t('label.assignee-plural')}:{' '}
</span> </Typography.Text>
<AssigneeList <OwnerLabel owners={thread.task.assignees} />
assignees={thread.task.assignees || []} </div>
/>
</Space>
)} )}
</Card> </Card>
</Fragment> </Fragment>

View File

@ -34,9 +34,9 @@ jest.mock('../ActivityFeedProvider/ActivityFeedProvider', () => ({
default: 'ActivityFeedProvider', default: 'ActivityFeedProvider',
})); }));
jest.mock('../../../components/common/AssigneeList/AssigneeList', () => { jest.mock('../../common/OwnerLabel/OwnerLabel.component', () => ({
return jest.fn().mockImplementation(() => <p>AssigneeList</p>); OwnerLabel: jest.fn().mockReturnValue(<p>OwnerLabel</p>),
}); }));
jest.mock('../../../components/common/PopOverCard/EntityPopOverCard', () => { jest.mock('../../../components/common/PopOverCard/EntityPopOverCard', () => {
return jest.fn().mockImplementation(({ children }) => children); return jest.fn().mockImplementation(({ children }) => children);
@ -102,4 +102,15 @@ describe('Test TaskFeedCard Component', () => {
expect(screen.getByTestId('task-status-icon-open')).toBeInTheDocument(); expect(screen.getByTestId('task-status-icon-open')).toBeInTheDocument();
expect(screen.getByTestId('redirect-task-button-link')).toBeInTheDocument(); expect(screen.getByTestId('redirect-task-button-link')).toBeInTheDocument();
}); });
it('Should render OwnerLabel when show thread is true', async () => {
await act(async () => {
render(<TaskFeedCard {...mockProps} />, {
wrapper: MemoryRouter,
});
});
expect(screen.getByText('label.assignee-plural:')).toBeInTheDocument();
expect(screen.getByText('OwnerLabel')).toBeInTheDocument();
});
}); });

View File

@ -20,7 +20,6 @@ import { useHistory } from 'react-router-dom';
import { ReactComponent as TaskCloseIcon } from '../../../assets/svg/ic-close-task.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 TaskOpenIcon } from '../../../assets/svg/ic-open-task.svg';
import { ReactComponent as ThreadIcon } from '../../../assets/svg/thread.svg'; import { ReactComponent as ThreadIcon } from '../../../assets/svg/thread.svg';
import AssigneeList from '../../../components/common/AssigneeList/AssigneeList';
import EntityPopOverCard from '../../../components/common/PopOverCard/EntityPopOverCard'; import EntityPopOverCard from '../../../components/common/PopOverCard/EntityPopOverCard';
import UserPopOverCard from '../../../components/common/PopOverCard/UserPopOverCard'; import UserPopOverCard from '../../../components/common/PopOverCard/UserPopOverCard';
import { import {
@ -38,6 +37,7 @@ import { getEntityFQN, getEntityType } from '../../../utils/FeedUtils';
import { TASK_TYPES } from '../../../constants/Task.constant'; import { TASK_TYPES } from '../../../constants/Task.constant';
import { getTaskDetailPath } from '../../../utils/TasksUtils'; import { getTaskDetailPath } from '../../../utils/TasksUtils';
import { OwnerLabel } from '../../common/OwnerLabel/OwnerLabel.component';
import ProfilePicture from '../../common/ProfilePicture/ProfilePicture'; import ProfilePicture from '../../common/ProfilePicture/ProfilePicture';
import { useActivityFeedProvider } from '../ActivityFeedProvider/ActivityFeedProvider'; import { useActivityFeedProvider } from '../ActivityFeedProvider/ActivityFeedProvider';
import ActivityFeedActions from '../Shared/ActivityFeedActions'; import ActivityFeedActions from '../Shared/ActivityFeedActions';
@ -206,7 +206,7 @@ const TaskFeedCard = ({
</Col> </Col>
{!showThread ? ( {!showThread ? (
<Col span={24}> <Col span={24}>
<div className="d-flex items-center p-l-lg gap-2"> <div className="d-flex items-start p-l-lg gap-2">
{postLength > 0 && ( {postLength > 0 && (
<> <>
<div className="thread-users-profile-pic"> <div className="thread-users-profile-pic">
@ -228,24 +228,23 @@ const TaskFeedCard = ({
className="d-flex items-center thread-count cursor-pointer m-l-xs" className="d-flex items-center thread-count cursor-pointer m-l-xs"
onClick={!hidePopover ? showReplies : noop}> onClick={!hidePopover ? showReplies : noop}>
<ThreadIcon width={20} />{' '} <ThreadIcon width={20} />{' '}
<span className="text-xs p-l-xss">{postLength}</span> <span className="text-xs p-t-xss p-l-xss">
{postLength}
</span>
</div> </div>
</> </>
)} )}
<Typography.Text <Typography.Text
className={ className={classNames(
'p-t-xss',
postLength > 0 postLength > 0
? 'm-l-sm text-sm text-grey-muted' ? 'm-l-sm text-sm text-grey-muted'
: 'text-sm text-grey-muted' : 'text-sm text-grey-muted'
}> )}>
{`${t('label.assignee-plural')}: `} {`${t('label.assignee-plural')}: `}
</Typography.Text> </Typography.Text>
<AssigneeList <OwnerLabel className="p-t-05" owners={feed?.task?.assignees} />
assignees={feed?.task?.assignees || []}
className="d-flex gap-1"
showUserName={false}
/>
</div> </div>
</Col> </Col>
) : null} ) : null}

View File

@ -54,10 +54,6 @@ jest.mock('../../../ActivityFeed/ActivityFeedEditor/ActivityFeedEditor', () => {
)); ));
}); });
jest.mock('../../../common/AssigneeList/AssigneeList', () => {
return jest.fn().mockImplementation(() => <p>AssigneeList</p>);
});
jest.mock('../../../common/OwnerLabel/OwnerLabel.component', () => ({ jest.mock('../../../common/OwnerLabel/OwnerLabel.component', () => ({
OwnerLabel: jest.fn().mockImplementation(() => <p>OwnerLabel</p>), OwnerLabel: jest.fn().mockImplementation(() => <p>OwnerLabel</p>),
})); }));
@ -171,6 +167,16 @@ describe('Test TaskFeedCard component', () => {
expect(activityFeedCard).toBeInTheDocument(); expect(activityFeedCard).toBeInTheDocument();
}); });
it('Should render the assignee and creator of task', async () => {
render(<TaskTab {...mockProps} />, {
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 () => { it('should not render task action button to the task owner if task has reviewer', async () => {
render(<TaskTab {...mockProps} hasGlossaryReviewer />, { render(<TaskTab {...mockProps} hasGlossaryReviewer />, {
wrapper: MemoryRouter, wrapper: MemoryRouter,

View File

@ -103,7 +103,6 @@ import ActivityFeedEditor, {
EditorContentRef, EditorContentRef,
} from '../../../ActivityFeed/ActivityFeedEditor/ActivityFeedEditor'; } from '../../../ActivityFeed/ActivityFeedEditor/ActivityFeedEditor';
import { useActivityFeedProvider } from '../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider'; import { useActivityFeedProvider } from '../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
import AssigneeList from '../../../common/AssigneeList/AssigneeList';
import InlineEdit from '../../../common/InlineEdit/InlineEdit.component'; import InlineEdit from '../../../common/InlineEdit/InlineEdit.component';
import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component'; import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component';
import EntityPopOverCard from '../../../common/PopOverCard/EntityPopOverCard'; import EntityPopOverCard from '../../../common/PopOverCard/EntityPopOverCard';
@ -144,7 +143,7 @@ export const TaskTab = ({
const { isAdminUser } = useAuth(); const { isAdminUser } = useAuth();
const { const {
postFeed, postFeed,
setActiveThread, updateEntityThread,
fetchUpdatedThread, fetchUpdatedThread,
updateTestCaseIncidentStatus, updateTestCaseIncidentStatus,
testCaseResolutionStatus, testCaseResolutionStatus,
@ -233,7 +232,7 @@ export const TaskTab = ({
const [comment, setComment] = useState(''); const [comment, setComment] = useState('');
const [isEditAssignee, setIsEditAssignee] = useState<boolean>(false); const [isEditAssignee, setIsEditAssignee] = useState<boolean>(false);
const [options, setOptions] = useState<Option[]>([]); const [options, setOptions] = useState<Option[]>([]);
const [isAssigneeLoading, setIsAssigneeLoading] = useState<boolean>(false);
const { initialAssignees, assigneeOptions } = useMemo(() => { const { initialAssignees, assigneeOptions } = useMemo(() => {
const initialAssignees = generateOptions(taskDetails?.assignees ?? []); const initialAssignees = generateOptions(taskDetails?.assignees ?? []);
const assigneeOptions = unionBy( const assigneeOptions = unionBy(
@ -773,6 +772,7 @@ export const TaskTab = ({
}, [taskDetails, isTaskDescription]); }, [taskDetails, isTaskDescription]);
const handleAssigneeUpdate = async () => { const handleAssigneeUpdate = async () => {
setIsAssigneeLoading(true);
const updatedTaskThread = { const updatedTaskThread = {
...taskThread, ...taskThread,
task: { task: {
@ -787,9 +787,11 @@ export const TaskTab = ({
const patch = compare(taskThread, updatedTaskThread); const patch = compare(taskThread, updatedTaskThread);
const data = await updateThread(taskThread.id, patch); const data = await updateThread(taskThread.id, patch);
setIsEditAssignee(false); setIsEditAssignee(false);
setActiveThread(data); updateEntityThread(data);
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
} finally {
setIsAssigneeLoading(false);
} }
}; };
@ -806,12 +808,13 @@ export const TaskTab = ({
<TaskTabIncidentManagerHeader thread={taskThread} /> <TaskTabIncidentManagerHeader thread={taskThread} />
) : ( ) : (
<div <div
className={classNames('d-flex justify-between', { className={classNames('d-flex justify-between flex-wrap gap-2', {
'flex-column': isEditAssignee, 'flex-column': isEditAssignee,
})}> })}>
<div className={classNames('gap-2', { 'flex-center': !isEditAssignee })}> <div className="d-flex gap-2" data-testid="task-assignees">
{isEditAssignee ? ( {isEditAssignee ? (
<Form <Form
className="w-full"
form={assigneesForm} form={assigneesForm}
layout="vertical" layout="vertical"
onFinish={handleAssigneeUpdate}> onFinish={handleAssigneeUpdate}>
@ -830,6 +833,7 @@ export const TaskTab = ({
<InlineEdit <InlineEdit
className="assignees-edit-input" className="assignees-edit-input"
direction="horizontal" direction="horizontal"
isLoading={isAssigneeLoading}
onCancel={() => { onCancel={() => {
setIsEditAssignee(false); setIsEditAssignee(false);
assigneesForm.setFieldValue('assignees', initialAssignees); assigneesForm.setFieldValue('assignees', initialAssignees);
@ -859,15 +863,12 @@ export const TaskTab = ({
<Typography.Text className="text-grey-muted"> <Typography.Text className="text-grey-muted">
{t('label.assignee-plural')}:{' '} {t('label.assignee-plural')}:{' '}
</Typography.Text> </Typography.Text>
<AssigneeList <OwnerLabel owners={taskDetails?.assignees} />
assignees={taskDetails?.assignees ?? []}
showUserName={false}
/>
{(isCreator || hasEditAccess) && {(isCreator || hasEditAccess) &&
!isTaskClosed && !isTaskClosed &&
owners.length === 0 ? ( owners.length === 0 ? (
<Button <Button
className="flex-center p-0" className="flex-center p-0 h-auto"
data-testid="edit-assignees" data-testid="edit-assignees"
icon={<EditIcon color={DE_ACTIVE_COLOR} width="14px" />} icon={<EditIcon color={DE_ACTIVE_COLOR} width="14px" />}
size="small" size="small"
@ -878,7 +879,7 @@ export const TaskTab = ({
</> </>
)} )}
</div> </div>
<div className={classNames('gap-2', { 'flex-center': !isEditAssignee })}> <div className="d-flex gap-2">
<Typography.Text className="text-grey-muted"> <Typography.Text className="text-grey-muted">
{t('label.created-by')}:{' '} {t('label.created-by')}:{' '}
</Typography.Text> </Typography.Text>

View File

@ -21,7 +21,6 @@ import { TestCaseResolutionStatusTypes } from '../../../../generated/tests/testC
import { formatDateTime } from '../../../../utils/date-time/DateTimeUtils'; import { formatDateTime } from '../../../../utils/date-time/DateTimeUtils';
import { getEntityName } from '../../../../utils/EntityUtils'; import { getEntityName } from '../../../../utils/EntityUtils';
import { useActivityFeedProvider } from '../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider'; import { useActivityFeedProvider } from '../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
import AssigneeList from '../../../common/AssigneeList/AssigneeList';
import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component'; import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component';
import RichTextEditorPreviewer from '../../../common/RichTextEditor/RichTextEditorPreviewer'; import RichTextEditorPreviewer from '../../../common/RichTextEditor/RichTextEditorPreviewer';
import Severity from '../../../DataQuality/IncidentManager/Severity/Severity.component'; import Severity from '../../../DataQuality/IncidentManager/Severity/Severity.component';
@ -136,7 +135,7 @@ const TaskTabIncidentManagerHeader = ({ thread }: { thread: Thread }) => {
isEmpty(thread.task?.assignees) ? ( isEmpty(thread.task?.assignees) ? (
NO_DATA_PLACEHOLDER NO_DATA_PLACEHOLDER
) : ( ) : (
<AssigneeList assignees={thread.task?.assignees ?? []} /> <OwnerLabel owners={thread.task?.assignees} />
)} )}
</div> </div>
<div className="gap-2 flex-center"> <div className="gap-2 flex-center">

View File

@ -151,9 +151,6 @@ jest.mock('../../../common/OwnerLabel/OwnerLabel.component', () => {
.mockImplementation(() => <div>OwnerLabel.component</div>), .mockImplementation(() => <div>OwnerLabel.component</div>),
}; };
}); });
jest.mock('../../../common/AssigneeList/AssigneeList', () => {
return jest.fn().mockImplementation(() => <div>AssigneeList.component</div>);
});
jest.mock( jest.mock(
'../../../DataQuality/IncidentManager/Severity/Severity.component', '../../../DataQuality/IncidentManager/Severity/Severity.component',
() => { () => {
@ -177,10 +174,7 @@ describe('Test TaskTabIncidentManagerHeader component', () => {
await screen.findByTestId('task-resolution-steps') await screen.findByTestId('task-resolution-steps')
).toBeInTheDocument(); ).toBeInTheDocument();
expect(await screen.findByTestId('failure-reason')).toBeInTheDocument(); expect(await screen.findByTestId('failure-reason')).toBeInTheDocument();
expect(await screen.findByText('OwnerLabel.component')).toBeInTheDocument(); expect(await screen.findAllByText('OwnerLabel.component')).toHaveLength(2);
expect(
await screen.findByText('AssigneeList.component')
).toBeInTheDocument();
expect(await screen.findByText('Severity.component')).toBeInTheDocument(); expect(await screen.findByText('Severity.component')).toBeInTheDocument();
expect( expect(
await screen.findByText('RichTextEditorPreviewer.component') await screen.findByText('RichTextEditorPreviewer.component')

View File

@ -1,24 +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 { HTMLAttributes } from 'react';
import { EntityReference } from '../../../generated/entity/type';
export interface AssigneeListProps extends HTMLAttributes<HTMLDivElement> {
assignees: EntityReference[];
showUserName?: boolean;
}
export enum UserTeam {
User = 'user',
Team = 'team',
}

View File

@ -1,38 +0,0 @@
/*
* Copyright 2022 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 React, { FC } from 'react';
import { getEntityName } from '../../../utils/EntityUtils';
import UserPopOverCard from '../PopOverCard/UserPopOverCard';
import { AssigneeListProps, UserTeam } from './AssigneeList.interface';
const AssigneeList: FC<AssigneeListProps> = ({
assignees,
showUserName = true,
}) => {
return (
<div className="d-flex gap-1 flex-wrap">
{assignees.map((assignee) => (
<UserPopOverCard
displayName={getEntityName(assignee)}
key={assignee.name}
showUserName={showUserName}
type={assignee.type as UserTeam}
userName={assignee.name ?? ''}
/>
))}
</div>
);
};
export default AssigneeList;

View File

@ -134,7 +134,7 @@ export const OwnerLabel = ({
})} })}
{remainingOwnersCount > 0 && ( {remainingOwnersCount > 0 && (
<Button <Button
className="more-owners-button text-xs" className="more-owners-button text-xs h-auto"
size="small" size="small"
type="link" type="link"
onClick={() => setShowAllOwners(!showAllOwners)}> onClick={() => setShowAllOwners(!showAllOwners)}>

View File

@ -33,6 +33,7 @@ import {
TERM_ADMIN, TERM_ADMIN,
} from '../../../constants/constants'; } from '../../../constants/constants';
import { TabSpecificField } from '../../../enums/entity.enum'; import { TabSpecificField } from '../../../enums/entity.enum';
import { OwnerType } from '../../../enums/user.enum';
import { EntityReference } from '../../../generated/type/entityReference'; import { EntityReference } from '../../../generated/type/entityReference';
import { useApplicationStore } from '../../../hooks/useApplicationStore'; import { useApplicationStore } from '../../../hooks/useApplicationStore';
import { useUserProfile } from '../../../hooks/user-profile/useUserProfile'; import { useUserProfile } from '../../../hooks/user-profile/useUserProfile';
@ -40,7 +41,6 @@ import { getUserByName } from '../../../rest/userAPI';
import { getNonDeletedTeams } from '../../../utils/CommonUtils'; import { getNonDeletedTeams } from '../../../utils/CommonUtils';
import { getEntityName } from '../../../utils/EntityUtils'; import { getEntityName } from '../../../utils/EntityUtils';
import { getUserWithImage } from '../../../utils/UserDataUtils'; import { getUserWithImage } from '../../../utils/UserDataUtils';
import { UserTeam } from '../AssigneeList/AssigneeList.interface';
import Loader from '../Loader/Loader'; import Loader from '../Loader/Loader';
import ProfilePicture from '../ProfilePicture/ProfilePicture'; import ProfilePicture from '../ProfilePicture/ProfilePicture';
@ -107,12 +107,12 @@ const UserRoles = React.memo(({ userName }: { userName: string }) => {
const PopoverContent = React.memo( const PopoverContent = React.memo(
({ ({
userName, userName,
type = UserTeam.User, type = OwnerType.USER,
}: { }: {
userName: string; userName: string;
type: UserTeam; type: OwnerType;
}) => { }) => {
const isTeam = type === UserTeam.Team; const isTeam = type === OwnerType.TEAM;
const [, , user = {}] = useUserProfile({ const [, , user = {}] = useUserProfile({
permission: true, permission: true,
name: userName, name: userName,
@ -175,18 +175,18 @@ const PopoverTitle = React.memo(
({ ({
userName, userName,
profilePicture, profilePicture,
type = UserTeam.User, type = OwnerType.USER,
}: { }: {
userName: string; userName: string;
profilePicture: JSX.Element; profilePicture: JSX.Element;
type: UserTeam; type: OwnerType;
}) => { }) => {
const history = useHistory(); const history = useHistory();
const [, , userData] = useUserProfile({ const [, , userData] = useUserProfile({
permission: true, permission: true,
name: userName, name: userName,
isTeam: type === UserTeam.Team, isTeam: type === OwnerType.TEAM,
}); });
const onTitleClickHandler = (path: string) => { const onTitleClickHandler = (path: string) => {
@ -221,7 +221,7 @@ const PopoverTitle = React.memo(
interface Props extends HTMLAttributes<HTMLDivElement> { interface Props extends HTMLAttributes<HTMLDivElement> {
userName: string; userName: string;
displayName?: ReactNode; displayName?: ReactNode;
type?: UserTeam; type?: OwnerType;
showUserName?: boolean; showUserName?: boolean;
showUserProfile?: boolean; showUserProfile?: boolean;
profileWidth?: number; profileWidth?: number;
@ -231,7 +231,7 @@ interface Props extends HTMLAttributes<HTMLDivElement> {
const UserPopOverCard: FC<Props> = ({ const UserPopOverCard: FC<Props> = ({
userName, userName,
displayName, displayName,
type = UserTeam.User, type = OwnerType.USER,
showUserName = false, showUserName = false,
showUserProfile = true, showUserProfile = true,
children, children,
@ -241,7 +241,7 @@ const UserPopOverCard: FC<Props> = ({
const profilePicture = ( const profilePicture = (
<ProfilePicture <ProfilePicture
avatarType="outlined" avatarType="outlined"
isTeam={type === UserTeam.Team} isTeam={type === OwnerType.TEAM}
name={userName} name={userName}
width={`${profileWidth}`} width={`${profileWidth}`}
/> />
@ -271,7 +271,7 @@ const UserPopOverCard: FC<Props> = ({
)} )}
data-testid={userName} data-testid={userName}
to={ to={
type === UserTeam.Team type === OwnerType.TEAM
? getTeamAndUserDetailsPath(userName) ? getTeamAndUserDetailsPath(userName)
: getUserPath(userName ?? '') : getUserPath(userName ?? '')
}> }>

View File

@ -501,6 +501,9 @@
.p-t-xs { .p-t-xs {
padding-top: @padding-xs; padding-top: @padding-xs;
} }
.p-t-05 {
padding-top: 2px;
}
.p-t-xss { .p-t-xss {
padding-top: @padding-xss; padding-top: @padding-xss;
} }

View File

@ -25,7 +25,6 @@ import TurndownService from 'turndown';
import { ReactComponent as AddIcon } from '../assets/svg/added-icon.svg'; import { ReactComponent as AddIcon } from '../assets/svg/added-icon.svg';
import { ReactComponent as UpdatedIcon } from '../assets/svg/updated-icon.svg'; import { ReactComponent as UpdatedIcon } from '../assets/svg/updated-icon.svg';
import { MentionSuggestionsItem } from '../components/ActivityFeed/FeedEditor/FeedEditor.interface'; import { MentionSuggestionsItem } from '../components/ActivityFeed/FeedEditor/FeedEditor.interface';
import { UserTeam } from '../components/common/AssigneeList/AssigneeList.interface';
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
import { import {
EntityField, EntityField,
@ -41,6 +40,7 @@ import {
} from '../constants/Feeds.constants'; } from '../constants/Feeds.constants';
import { EntityType, FqnPart, TabSpecificField } from '../enums/entity.enum'; import { EntityType, FqnPart, TabSpecificField } from '../enums/entity.enum';
import { SearchIndex } from '../enums/search.enum'; import { SearchIndex } from '../enums/search.enum';
import { OwnerType } from '../enums/user.enum';
import { import {
CardStyle, CardStyle,
EntityTestResultSummaryObject, EntityTestResultSummaryObject,
@ -200,7 +200,8 @@ export async function suggestions(
ENTITY_URL_MAP[entityType as EntityUrlMapType], ENTITY_URL_MAP[entityType as EntityUrlMapType],
hit._source.name hit._source.name
), ),
type: hit._index === SearchIndex.USER ? UserTeam.User : UserTeam.Team, type:
hit._index === SearchIndex.USER ? OwnerType.USER : OwnerType.TEAM,
name: hit._source.name, name: hit._source.name,
displayName: hit._source.displayName, displayName: hit._source.displayName,
}; };

View File

@ -18,12 +18,12 @@ import { isUndefined } from 'lodash';
import { ServiceTypes } from 'Models'; import { ServiceTypes } from 'Models';
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { UserTeam } from '../components/common/AssigneeList/AssigneeList.interface';
import UserPopOverCard from '../components/common/PopOverCard/UserPopOverCard'; import UserPopOverCard from '../components/common/PopOverCard/UserPopOverCard';
import RichTextEditorPreviewer from '../components/common/RichTextEditor/RichTextEditorPreviewer'; import RichTextEditorPreviewer from '../components/common/RichTextEditor/RichTextEditorPreviewer';
import TagsViewer from '../components/Tag/TagsViewer/TagsViewer'; import TagsViewer from '../components/Tag/TagsViewer/TagsViewer';
import { NO_DATA_PLACEHOLDER } from '../constants/constants'; import { NO_DATA_PLACEHOLDER } from '../constants/constants';
import { ServiceCategory } from '../enums/service.enum'; import { ServiceCategory } from '../enums/service.enum';
import { OwnerType } from '../enums/user.enum';
import { Database } from '../generated/entity/data/database'; import { Database } from '../generated/entity/data/database';
import { Pipeline } from '../generated/entity/data/pipeline'; import { Pipeline } from '../generated/entity/data/pipeline';
import { EntityReference } from '../generated/entity/type'; import { EntityReference } from '../generated/entity/type';
@ -101,7 +101,7 @@ export const getServiceMainTabColumns = (
displayName={owner.displayName} displayName={owner.displayName}
key={owner.id} key={owner.id}
profileWidth={20} profileWidth={20}
type={owner.type as UserTeam} type={owner.type as OwnerType}
userName={owner.name ?? ''} userName={owner.name ?? ''}
/> />
)) ))