diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts index ebdde5a1e5d6..af0a1a08bb66 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/ActivityFeed.spec.ts @@ -16,12 +16,14 @@ import { UserClass } from '../../support/user/UserClass'; import { checkDescriptionInEditModal } from '../../utils/activityFeed'; import { createNewPage, + descriptionBox, performAdminLogin, performUserLogin, redirectToHomePage, toastNotification, visitUserProfilePage, } from '../../utils/common'; +import { updateDescription } from '../../utils/entity'; import { clickOnLogo } from '../../utils/sidebar'; import { createDescriptionTask, @@ -327,6 +329,7 @@ test.describe('Activity feed with Data Steward User', () => { const { afterAction, apiContext } = await performAdminLogin(browser); await entity.create(apiContext); + await entity2.create(apiContext); await user1.create(apiContext); await user2.create(apiContext); await afterAction(); @@ -335,13 +338,14 @@ test.describe('Activity feed with Data Steward User', () => { test.afterAll('Cleanup', async ({ browser }) => { const { afterAction, apiContext } = await performAdminLogin(browser); await entity.delete(apiContext); + await entity2.delete(apiContext); await user1.delete(apiContext); await user2.delete(apiContext); await afterAction(); }); - test('Create and Assign Task', async ({ browser }) => { + test('Create and Assign Task with Suggestions', async ({ browser }) => { const { page: page1, afterAction: afterActionUser1 } = await performUserLogin(browser, user1); const { page: page2, afterAction: afterActionUser2 } = @@ -473,4 +477,161 @@ test.describe('Activity feed with Data Steward User', () => { await afterActionUser2(); }); }); + + test('Create and Assign Task without Suggestions', async ({ browser }) => { + const { page: page1, afterAction: afterActionUser1 } = + await performUserLogin(browser, user1); + const { page: page2, afterAction: afterActionUser2 } = + await performUserLogin(browser, user2); + + const value: TaskDetails = { + term: entity2.entity.name, + assignee: user2.responseData.name, + }; + + await test.step('Create, Close and Assign Task to user 2', async () => { + await redirectToHomePage(page1); + await entity2.visitEntityPage(page1); + + await updateDescription(page1, ''); + + // Create 2 task for the same entity, one to close and 2nd for the user2 action + await page1.getByTestId('request-description').click(); + + await createDescriptionTask(page1, value, false); + + await page1.getByTestId('schema').click(); + + await page1.getByTestId('request-entity-tags').click(); + + // create tag task + await createTagTask(page1, value, false); + + // Should only see the close, add and comment button + expect( + await page1.locator('[data-testid="comment-button"]').isDisabled() + ).toBeTruthy(); + expect(page1.locator('[data-testid="close-button"]')).toBeVisible(); + expect( + page1.locator('[data-testid="edit-accept-task-dropdown"]') + ).not.toBeVisible(); + expect( + page1.locator('[data-testid="add-close-task-dropdown"]') + ).not.toBeVisible(); + + await afterActionUser1(); + }); + + await test.step( + 'Accept Task By user 2 with adding suggestions', + async () => { + await redirectToHomePage(page2); + + const taskResponse = page2.waitForResponse( + '/api/v1/feed?type=Task&filterType=OWNER&taskStatus=Open&userId=*' + ); + + await page2 + .getByTestId('activity-feed-widget') + .getByText('Tasks') + .click(); + + await taskResponse; + + await expect( + page2.locator( + '[data-testid="activity-feed-widget"] [data-testid="no-data-placeholder"]' + ) + ).not.toBeVisible(); + + const entityPageTaskTab = page2.waitForResponse( + '/api/v1/feed?*&type=Task' + ); + + const tagsTask = page2.getByTestId('redirect-task-button-link').first(); + const tagsTaskContent = await tagsTask.innerText(); + + expect(tagsTaskContent).toContain('Request tags for'); + + await tagsTask.click(); + await entityPageTaskTab; + + expect(page2.getByTestId('noDiff-placeholder')).toBeVisible(); + + // Should see the add_close dropdown and comment button + expect( + await page2.locator('[data-testid="comment-button"]').isDisabled() + ).toBeTruthy(); + await expect( + page2.getByTestId('add-close-task-dropdown') + ).toBeVisible(); + await expect( + page2.locator('[data-testid="close-button"]') + ).not.toBeVisible(); + await expect( + page2.locator('[data-testid="edit-accept-task-dropdown"]') + ).not.toBeVisible(); + + await page2.getByRole('button', { name: 'Add Tags' }).click(); + + await page2.waitForSelector('[role="dialog"].ant-modal'); + + const modalTitleContent = await page2 + .locator('.ant-modal-header .ant-modal-title') + .innerText(); + + expect(modalTitleContent).toContain( + `Request tags for table ${value.term}` + ); + + // select the Tag + const suggestTags = page2.locator( + '[data-testid="tag-selector"] > .ant-select-selector .ant-select-selection-search-input' + ); + await suggestTags.click(); + + const querySearchResponse = page2.waitForResponse( + `/api/v1/search/query?q=*${'PII.None'}*&index=tag_search_index&*` + ); + await suggestTags.fill('PII.None'); + + await querySearchResponse; + + // select value from dropdown + const dropdownValue = page2.getByTestId(`tag-PII.None`); + await dropdownValue.hover(); + await dropdownValue.click(); + + await expect(page2.getByTestId('selected-tag-PII.None')).toBeVisible(); + + await page2.getByText('OK').click(); + + await toastNotification(page2, /Task resolved successfully/); + + // Accept the description task + + await expect(page2.getByText('No Suggestion')).toBeVisible(); + + await page2.getByRole('button', { name: 'Add Description' }).click(); + + await page2.waitForSelector('[role="dialog"].ant-modal'); + + const modalTitleDescriptionContent = await page2 + .locator('.ant-modal-header .ant-modal-title') + .innerText(); + + expect(modalTitleDescriptionContent).toContain( + `Request description for table ${value.term}` + ); + + await page2.locator(descriptionBox).fill('New description'); + + await page2.getByText('OK').click(); + + await toastNotification(page2, /Task resolved successfully/); + + await afterActionUser2(); + } + ); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/task.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/task.ts index 6ab7b6895cfb..1f28fea97591 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/task.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/task.ts @@ -28,10 +28,11 @@ const tag = 'PII.None'; export const createDescriptionTask = async ( page: Page, value: TaskDetails, + addDescription = true, assigneeDisabled?: boolean ) => { expect(await page.locator('#title').inputValue()).toBe( - `Update description for table ${ + `${addDescription ? 'Update' : 'Request'} description for table ${ value.columnName ? `${value.term} columns/${value.columnName}` : value.term @@ -69,9 +70,11 @@ export const createDescriptionTask = async ( await page.click('body'); } - await page - .locator(descriptionBox) - .fill(value.description ?? 'Updated description'); + if (addDescription) { + await page + .locator(descriptionBox) + .fill(value.description ?? 'Updated description'); + } await page.click('button[type="submit"]'); await toastNotification(page, /Task created successfully./); @@ -80,6 +83,7 @@ export const createDescriptionTask = async ( export const createTagTask = async ( page: Page, value: TaskDetails, + addTag = true, assigneeDisabled?: boolean ) => { expect(await page.locator('#title').inputValue()).toBe( @@ -117,24 +121,26 @@ export const createTagTask = async ( await clickOutside(page); } - // select tags - const suggestTags = page.locator( - '[data-testid="tag-selector"] > .ant-select-selector .ant-select-selection-search-input' - ); - await suggestTags.click(); + if (addTag) { + // select tags + const suggestTags = page.locator( + '[data-testid="tag-selector"] > .ant-select-selector .ant-select-selection-search-input' + ); + await suggestTags.click(); - const querySearchResponse = page.waitForResponse( - `/api/v1/search/query?q=*${value.tag ?? tag}*&index=tag_search_index&*` - ); - await suggestTags.fill(value.tag ?? tag); + const querySearchResponse = page.waitForResponse( + `/api/v1/search/query?q=*${value.tag ?? tag}*&index=tag_search_index&*` + ); + await suggestTags.fill(value.tag ?? tag); - await querySearchResponse; + await querySearchResponse; - // select value from dropdown - const dropdownValue = page.getByTestId(`tag-${value.tag ?? tag}`); - await dropdownValue.hover(); - await dropdownValue.click(); - await clickOutside(page); + // select value from dropdown + const dropdownValue = page.getByTestId(`tag-${value.tag ?? tag}`); + await dropdownValue.hover(); + await dropdownValue.click(); + await clickOutside(page); + } await page.click('button[type="submit"]'); 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 index 57ffed8b5ae9..39605c041614 100644 --- 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 @@ -555,17 +555,34 @@ export const TaskTab = ({ } }; + 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 || - taskAction.key === TaskActionMode.EDIT - ) { + 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 ( + + )) +); + +jest.mock('./TagsDiffView', () => ({ + TagsDiffView: jest.fn().mockReturnValue(

TagsDiffView

), +})); + +jest.mock('./TagsTabs', () => ({ + TagsTabs: jest.fn().mockImplementation(({ onChange }) => ( +
+

TagsTabs

+ +
+ )), +})); + +const mockProps = { + task: TASK_FEED.task, + isTaskActionEdit: false, + hasEditAccess: false, + onChange: jest.fn(), +}; + +const mockTask = { + id: 2, + type: TaskType.RequestTag, + assignees: [ + { + id: '31d072f8-7873-4976-88ea-ac0d2f51f632', + type: 'team', + name: 'Sales', + fullyQualifiedName: 'Sales', + deleted: false, + }, + ], +}; + +describe('Test TagsTask Component', () => { + it('Should render the component', async () => { + render(); + + expect(screen.getByTestId('task-tags-tabs')).toBeInTheDocument(); + }); + + it('Should render TagsDiffView component if in not editMode, type is RequestTag and not having hasEditAccess', async () => { + render(); + + expect(screen.getByTestId('tags-task')).toBeInTheDocument(); + expect(screen.getByTestId('request-tags')).toBeInTheDocument(); + + expect(screen.getByText('TagsDiffView')).toBeInTheDocument(); + }); + + // eslint-disable-next-line max-len + it('Should render TagsDiffView component if in not editMode, type is RequestTag, not having hasEditAccess and if suggestion tags is present', async () => { + render( + + ); + + expect(screen.getByTestId('tags-task')).toBeInTheDocument(); + expect(screen.getByTestId('request-tags')).toBeInTheDocument(); + + expect(screen.getByText('TagsDiffView')).toBeInTheDocument(); + }); + + // eslint-disable-next-line max-len + it('Should render TagsDiffView component if in not editMode, type is RequestTag, not having hasEditAccess and if old tags is present', async () => { + render( + + ); + + expect(screen.getByTestId('tags-task')).toBeInTheDocument(); + expect(screen.getByTestId('request-tags')).toBeInTheDocument(); + + expect(screen.getByText('TagsDiffView')).toBeInTheDocument(); + }); + + // eslint-disable-next-line max-len + it('Should render noDataPlaceholder if in not editMode, type is RequestTag, not having hasEditAccess and do not have old and suggestion', async () => { + render(); + + expect(screen.getByTestId('tags-task')).toBeInTheDocument(); + expect(screen.getByTestId('request-tags')).toBeInTheDocument(); + + expect(screen.getByTestId('no-suggestion')).toBeInTheDocument(); + }); + + it('Should render TagsDiffView if not in editMode, type is RequestTag and having hasEditAccess', async () => { + render(); + + expect(screen.getByTestId('tags-task')).toBeInTheDocument(); + expect(screen.getByTestId('request-tags')).toBeInTheDocument(); + + expect(screen.getByText('TagsDiffView')).toBeInTheDocument(); + }); + + it('Should render TagSuggestion and call onChange if in editMode, type is RequestTag and having hasEditAccess', async () => { + render(); + + expect(screen.getByTestId('tags-task')).toBeInTheDocument(); + expect(screen.getByTestId('request-tags')).toBeInTheDocument(); + + expect(screen.getByText('TagSuggestion')).toBeInTheDocument(); + + fireEvent.click(screen.getByText('TagSuggestionButton')); + + expect(mockProps.onChange).toHaveBeenCalled(); + }); + + it('Should render no-suggestion if not in editMode, type is UpdateTag and not having hasEditAccess', async () => { + render( + + ); + + expect(screen.getByTestId('update-tags')).toBeInTheDocument(); + + expect(screen.getByTestId('no-suggestion')).toBeInTheDocument(); + }); + + it('Should render TagsTabs and call onChange if in editMode, type is UpdateTag and not having hasEditAccess', async () => { + render( + + ); + + expect(screen.getByTestId('update-tags')).toBeInTheDocument(); + + expect(screen.getByText('TagsTabs')).toBeInTheDocument(); + + fireEvent.click(screen.getByText('TagsTabsButton')); + + expect(mockProps.onChange).toHaveBeenCalled(); + }); + + it('Should render noDataPlaceholder if task is closed and no new and old value is there', async () => { + render( + + ); + + expect(screen.getByText('label.no-entity')).toBeInTheDocument(); + }); + + it('Should render TagsDiffView if task is closed and no new and old value is there', async () => { + render( + + ); + + expect(screen.getByText('TagsDiffView')).toBeInTheDocument(); + }); +}); 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 index ff902871592a..eb4877cbff5c 100644 --- 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 @@ -29,10 +29,12 @@ interface TagsTaskProps { task: Thread['task']; isTaskActionEdit: boolean; hasEditAccess: boolean; + value?: TagLabel[]; onChange: (newTags: TagLabel[]) => void; } const TagsTask: FC = ({ + value, isTaskActionEdit, hasEditAccess, task, @@ -76,7 +78,9 @@ const TagsTask: FC = ({ const suggestedTagsDiff = useMemo(() => { if (!suggestion && !oldValue) { return ( - + {t('label.no-entity', { entity: t('label.suggestion') })} ); @@ -102,10 +106,7 @@ const TagsTask: FC = ({ {isRequestTag && (
{isTaskActionEdit && hasEditAccess ? ( - + ) : ( suggestedTagsDiff )} @@ -116,7 +117,7 @@ const TagsTask: FC = ({ {isTaskActionEdit && hasEditAccess ? ( ) : (