mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-29 17:49:14 +00:00
* change placment of comment and close button in task approval workflow * minor change * playwright test for the close and comment function * supported ref in activityFeedEditor * fix playwright test * added playwright test for data steward * fix the test for the data streward user * fix the close button not showing if task has no suggestions and icon fixes * fix sonar issue * change glossary and add suggestion button to dropdown button * fix the glossary failure due to button change * icon change for add tag and description * fix glossary cypress failure due to button chnages * changes as per comments
This commit is contained in:
parent
b19b7f59a5
commit
f500e70256
@ -382,7 +382,10 @@ const approveGlossaryTermWorkflow = ({ glossary, glossaryTerm }) => {
|
||||
|
||||
interceptURL('PUT', '/api/v1/feed/tasks/*/resolve', 'resolveTask');
|
||||
|
||||
cy.get('[data-testid="approve-task"]').click();
|
||||
// approve the task
|
||||
cy.get(
|
||||
'[data-testid="glossary-accept-reject-task-dropdown"] .ant-btn-compact-first-item > span'
|
||||
).click();
|
||||
|
||||
verifyResponseStatusCode('@resolveTask', 200);
|
||||
|
||||
|
||||
@ -16,6 +16,8 @@ import { UserClass } from '../../support/user/UserClass';
|
||||
import { checkDescriptionInEditModal } from '../../utils/activityFeed';
|
||||
import {
|
||||
createNewPage,
|
||||
performAdminLogin,
|
||||
performUserLogin,
|
||||
redirectToHomePage,
|
||||
toastNotification,
|
||||
visitUserProfilePage,
|
||||
@ -27,18 +29,21 @@ import {
|
||||
TaskDetails,
|
||||
} from '../../utils/task';
|
||||
|
||||
// use the admin user to login
|
||||
test.use({ storageState: 'playwright/.auth/admin.json' });
|
||||
|
||||
const entity = new TableClass();
|
||||
const user = new UserClass();
|
||||
const entity2 = new TableClass();
|
||||
const user1 = new UserClass();
|
||||
const user2 = new UserClass();
|
||||
|
||||
test.describe('Activity feed', () => {
|
||||
// use the admin user to login
|
||||
test.use({ storageState: 'playwright/.auth/admin.json' });
|
||||
|
||||
test.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await createNewPage(browser);
|
||||
|
||||
await entity.create(apiContext);
|
||||
await user.create(apiContext);
|
||||
await entity2.create(apiContext);
|
||||
await user1.create(apiContext);
|
||||
|
||||
await afterAction();
|
||||
});
|
||||
@ -50,7 +55,8 @@ test.describe('Activity feed', () => {
|
||||
test.afterAll('Cleanup', async ({ browser }) => {
|
||||
const { apiContext, afterAction } = await createNewPage(browser);
|
||||
await entity.delete(apiContext);
|
||||
await user.delete(apiContext);
|
||||
await entity2.delete(apiContext);
|
||||
await user1.delete(apiContext);
|
||||
|
||||
await afterAction();
|
||||
});
|
||||
@ -58,7 +64,7 @@ test.describe('Activity feed', () => {
|
||||
test('Assigned task should appear to task tab', async ({ page }) => {
|
||||
const value: TaskDetails = {
|
||||
term: entity.entity.name,
|
||||
assignee: user.responseData.name,
|
||||
assignee: user1.responseData.name,
|
||||
};
|
||||
await entity.visitEntityPage(page);
|
||||
|
||||
@ -186,7 +192,7 @@ test.describe('Activity feed', () => {
|
||||
test('Update Description Task on Columns', async ({ page }) => {
|
||||
const firstTaskValue: TaskDetails = {
|
||||
term: entity.entity.name,
|
||||
assignee: user.responseData.name,
|
||||
assignee: user1.responseData.name,
|
||||
description: 'Column Description 1',
|
||||
columnName: entity.entity.columns[0].name,
|
||||
oldDescription: entity.entity.columns[0].description,
|
||||
@ -246,4 +252,225 @@ test.describe('Activity feed', () => {
|
||||
|
||||
expect(closedTask).toContain('2 Closed');
|
||||
});
|
||||
|
||||
test('Comment and Close Task should work in Task Flow', async ({ page }) => {
|
||||
const value: TaskDetails = {
|
||||
term: entity2.entity.name,
|
||||
assignee: user1.responseData.name,
|
||||
};
|
||||
await entity2.visitEntityPage(page);
|
||||
|
||||
await page.getByTestId('request-description').click();
|
||||
|
||||
await createDescriptionTask(page, value);
|
||||
|
||||
// Task 1 - Update Description right panel check
|
||||
const descriptionTask = await page.getByTestId('task-title').innerText();
|
||||
|
||||
expect(descriptionTask).toContain('Request to update description');
|
||||
|
||||
// Check the editor send button is not visible and comment button is disabled when no text is added
|
||||
expect(page.locator('[data-testid="send-button"]')).not.toBeVisible();
|
||||
expect(
|
||||
await page.locator('[data-testid="comment-button"]').isDisabled()
|
||||
).toBeTruthy();
|
||||
|
||||
await page.fill(
|
||||
'[data-testid="editor-wrapper"] .ql-editor',
|
||||
'Test comment added'
|
||||
);
|
||||
const addComment = page.waitForResponse('/api/v1/feed/*/posts');
|
||||
await page.getByTestId('comment-button').click();
|
||||
await addComment;
|
||||
|
||||
// Close the task from the Button.Group, should throw error when no comment is added.
|
||||
await page.getByRole('button', { name: 'down' }).click();
|
||||
await page.waitForSelector('.ant-dropdown', {
|
||||
state: 'visible',
|
||||
});
|
||||
|
||||
await page.getByRole('menuitem', { name: 'close' }).click();
|
||||
|
||||
await toastNotification(page, 'Task cannot be closed without a comment.');
|
||||
|
||||
// Close the task from the Button.Group, with comment is added.
|
||||
await page.fill(
|
||||
'[data-testid="editor-wrapper"] .ql-editor',
|
||||
'Closing the task with comment'
|
||||
);
|
||||
const commentWithCloseTask = page.waitForResponse(
|
||||
'/api/v1/feed/tasks/*/close'
|
||||
);
|
||||
await page.getByRole('button', { name: 'down' }).click();
|
||||
await page.waitForSelector('.ant-dropdown', {
|
||||
state: 'visible',
|
||||
});
|
||||
await page.getByRole('menuitem', { name: 'close' }).click();
|
||||
await commentWithCloseTask;
|
||||
|
||||
await toastNotification(page, 'Task closed successfully.');
|
||||
|
||||
const openTask = await page.getByTestId('open-task').textContent();
|
||||
|
||||
expect(openTask).toContain('0 Open');
|
||||
|
||||
const closedTask = await page.getByTestId('closed-task').textContent();
|
||||
|
||||
expect(closedTask).toContain('1 Closed');
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Activity feed with Data Steward User', () => {
|
||||
test.slow(true);
|
||||
|
||||
test.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||
const { afterAction, apiContext } = await performAdminLogin(browser);
|
||||
|
||||
await entity.create(apiContext);
|
||||
await user1.create(apiContext);
|
||||
await user2.create(apiContext);
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test.afterAll('Cleanup', async ({ browser }) => {
|
||||
const { afterAction, apiContext } = await performAdminLogin(browser);
|
||||
await entity.delete(apiContext);
|
||||
await user1.delete(apiContext);
|
||||
await user2.delete(apiContext);
|
||||
|
||||
await afterAction();
|
||||
});
|
||||
|
||||
test('Create and Assign Task', async ({ browser }) => {
|
||||
const { page: page1, afterAction: afterActionUser1 } =
|
||||
await performUserLogin(browser, user1);
|
||||
const { page: page2, afterAction: afterActionUser2 } =
|
||||
await performUserLogin(browser, user2);
|
||||
|
||||
const value: TaskDetails = {
|
||||
term: entity.entity.name,
|
||||
assignee: user2.responseData.name,
|
||||
};
|
||||
|
||||
await test.step('Create, Close and Assign Task to User 2', async () => {
|
||||
await redirectToHomePage(page1);
|
||||
await entity.visitEntityPage(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);
|
||||
|
||||
await page1.getByTestId('schema').click();
|
||||
|
||||
await page1.getByTestId('request-entity-tags').click();
|
||||
|
||||
// create tag task
|
||||
await createTagTask(page1, { ...value, tag: 'PII.None' });
|
||||
|
||||
// Should only see the close 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();
|
||||
|
||||
// Close 1st task
|
||||
await page1.fill(
|
||||
'[data-testid="editor-wrapper"] .ql-editor',
|
||||
'Closing the task with comment'
|
||||
);
|
||||
const commentWithCloseTask = page1.waitForResponse(
|
||||
'/api/v1/feed/tasks/*/close'
|
||||
);
|
||||
page1.locator('[data-testid="close-button"]').click();
|
||||
await commentWithCloseTask;
|
||||
|
||||
// TODO: Ashish - Fix the toast notification once issue is resolved from Backend https://github.com/open-metadata/OpenMetadata/issues/17059
|
||||
|
||||
// await toastNotification(page1, 'Task closed successfully.');
|
||||
await toastNotification(
|
||||
page1,
|
||||
'An exception with message [Cannot invoke "org.openmetadata.schema.type.EntityReference.getName()" because "owner" is null] was thrown while processing request.'
|
||||
);
|
||||
|
||||
// TODO: Ashish - Enable them once issue is resolved from Backend https://github.com/open-metadata/OpenMetadata/issues/17059
|
||||
// const openTask = await page1.getByTestId('open-task').textContent();
|
||||
// expect(openTask).toContain('1 Open');
|
||||
// const closedTask = await page1.getByTestId('closed-task').textContent();
|
||||
// expect(closedTask).toContain('1 Closed');
|
||||
|
||||
await afterActionUser1();
|
||||
});
|
||||
|
||||
await test.step('Accept Task By User 2', 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;
|
||||
|
||||
// TODO: Ashish - Enable them once issue is resolved from Backend https://github.com/open-metadata/OpenMetadata/issues/17059
|
||||
// Count for task should be 1 both open and closed
|
||||
|
||||
// const openTaskBefore = await page2.getByTestId('open-task').textContent();
|
||||
// expect(openTaskBefore).toContain('1 Open');
|
||||
|
||||
// const closedTaskBefore = await page2
|
||||
// .getByTestId('closed-task')
|
||||
// .textContent();
|
||||
// expect(closedTaskBefore).toContain('1 Closed');
|
||||
|
||||
// Should not see the close button
|
||||
expect(page2.locator('[data-testid="close-button"]')).not.toBeVisible();
|
||||
|
||||
expect(
|
||||
await page2.locator('[data-testid="comment-button"]').isDisabled()
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
page2.locator('[data-testid="edit-accept-task-dropdown"]')
|
||||
).toBeVisible();
|
||||
|
||||
await page2.getByText('Accept Suggestion').click();
|
||||
|
||||
await toastNotification(page2, /Task resolved successfully/);
|
||||
|
||||
// TODO: Ashish - Enable them once issue is resolved from Backend https://github.com/open-metadata/OpenMetadata/issues/17059
|
||||
// const openTask = await page2.getByTestId('open-task').textContent();
|
||||
// expect(openTask).toContain('0 Open');
|
||||
|
||||
const closedTask = await page2.getByTestId('closed-task').textContent();
|
||||
|
||||
expect(closedTask).toContain('1 Closed');
|
||||
|
||||
await afterActionUser2();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -150,7 +150,7 @@ export const clickOutside = async (page: Page) => {
|
||||
|
||||
export const visitUserProfilePage = async (page: Page) => {
|
||||
await page.getByTestId('dropdown-profile').click();
|
||||
await page.waitForSelector('.profile-dropdown', {
|
||||
await page.waitForSelector('[role="menu"].profile-dropdown', {
|
||||
state: 'visible',
|
||||
});
|
||||
const userResponse = page.waitForResponse(
|
||||
|
||||
@ -553,7 +553,7 @@ export const approveGlossaryTermTask = async (
|
||||
) => {
|
||||
await validateGlossaryTermTask(page, term);
|
||||
const taskResolve = page.waitForResponse('/api/v1/feed/tasks/*/resolve');
|
||||
await page.click('[data-testid="approve-task"]');
|
||||
await page.getByRole('button', { name: 'Approve' }).click();
|
||||
await taskResolve;
|
||||
|
||||
// Display toast notification
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_7784_244249)">
|
||||
<path d="M10 0C4.48357 0 0 4.48357 0 10C0 15.5164 4.48357 20 10 20C15.5164 20 20 15.5164 20 10C20 4.48357 15.5164 0 10 0ZM10 18.9906C5.04695 18.9906 1.00939 14.9531 1.00939 10C1.00939 5.04695 5.04695 1.00939 10 1.00939C14.9531 1.00939 18.9906 5.04695 18.9906 10C18.9906 14.9531 14.9531 18.9906 10 18.9906Z" fill="#757575"/>
|
||||
<path d="M10.7042 9.95317L13.6385 7.0189C13.8263 6.83111 13.8263 6.50247 13.6385 6.31467C13.4507 6.12688 13.1221 6.12688 12.9343 6.31467L10 9.24895L7.06573 6.31467C6.87793 6.12688 6.5493 6.12688 6.3615 6.31467C6.17371 6.50247 6.17371 6.83111 6.3615 7.0189L9.29577 9.95317L6.3615 12.864C6.17371 13.0518 6.17371 13.3804 6.3615 13.5682C6.4554 13.6621 6.59624 13.709 6.71362 13.709C6.83099 13.709 6.97183 13.6621 7.06573 13.5682L10 10.6339L12.9343 13.5682C13.0282 13.6621 13.169 13.709 13.2864 13.709C13.4038 13.709 13.5446 13.6621 13.6385 13.5682C13.8263 13.3804 13.8263 13.0518 13.6385 12.864L10.7042 9.95317Z" fill="#757575"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_7784_244249">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.7 KiB |
@ -0,0 +1,11 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_8185_248203)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 5.58203C10.3853 5.58203 10.6977 5.89439 10.6977 6.27971V9.30296H13.7209C14.1062 9.30296 14.4186 9.61533 14.4186 10.0006C14.4186 10.3859 14.1062 10.6983 13.7209 10.6983H10.6977V13.7216C10.6977 14.1069 10.3853 14.4192 10 14.4192C9.61469 14.4192 9.30232 14.1069 9.30232 13.7216V10.6983H6.27906C5.89375 10.6983 5.58139 10.3859 5.58139 10.0006C5.58139 9.61533 5.89375 9.30296 6.27906 9.30296H9.30232V6.27971C9.30232 5.89439 9.61469 5.58203 10 5.58203Z" fill="#48CA9E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 18.6047C5.24779 18.6047 1.39535 14.7522 1.39535 10C1.39535 5.24779 5.24779 1.39535 10 1.39535C14.7522 1.39535 18.6047 5.24779 18.6047 10C18.6047 14.7522 14.7522 18.6047 10 18.6047ZM0 10C0 15.5229 4.47715 20 10 20C15.5229 20 20 15.5229 20 10C20 4.47715 15.5229 0 10 0C4.47715 0 0 4.47715 0 10Z" fill="#48CA9E"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_8185_248203">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="10" cy="10" r="9.5" fill="white" stroke="#48CA9E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.6869 6.31246C15.8821 6.50772 15.8821 6.82431 15.6869 7.01957L10.0042 12.7023C9.45255 13.2539 8.57016 13.2906 7.97461 12.7867L4.3437 9.71438C4.13289 9.536 4.1066 9.22051 4.28497 9.00971C4.46335 8.79891 4.77883 8.77262 4.98964 8.95099L8.62056 12.0233C8.81907 12.1913 9.1132 12.179 9.29708 11.9952L14.9798 6.31246C15.175 6.1172 15.4916 6.1172 15.6869 6.31246Z" fill="#48CA9E"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 571 B |
@ -12,8 +12,15 @@
|
||||
*/
|
||||
|
||||
import classNames from 'classnames';
|
||||
import React, { FC, HTMLAttributes, useRef, useState } from 'react';
|
||||
import React, {
|
||||
forwardRef,
|
||||
HTMLAttributes,
|
||||
useImperativeHandle,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { getBackendFormat, HTMLToMarkdown } from '../../../utils/FeedUtils';
|
||||
import { editorRef } from '../../common/RichTextEditor/RichTextEditor.interface';
|
||||
import { FeedEditor } from '../FeedEditor/FeedEditor';
|
||||
import { KeyHelp } from './KeyHelp';
|
||||
import { SendButton } from './SendButton';
|
||||
@ -33,60 +40,73 @@ export type EditorContentRef = {
|
||||
clearEditorValue: () => string;
|
||||
};
|
||||
|
||||
const ActivityFeedEditor: FC<ActivityFeedEditorProp> = ({
|
||||
className,
|
||||
editorClass,
|
||||
onSave,
|
||||
placeHolder,
|
||||
defaultValue,
|
||||
onTextChange,
|
||||
editAction,
|
||||
focused = false,
|
||||
}) => {
|
||||
const editorRef = useRef<EditorContentRef>();
|
||||
const [editorValue, setEditorValue] = useState<string>('');
|
||||
const ActivityFeedEditor = forwardRef<editorRef, ActivityFeedEditorProp>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
editorClass,
|
||||
onSave,
|
||||
placeHolder,
|
||||
defaultValue,
|
||||
onTextChange,
|
||||
editAction,
|
||||
focused = false,
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const editorRef = useRef<EditorContentRef>();
|
||||
const [editorValue, setEditorValue] = useState<string>('');
|
||||
|
||||
const onChangeHandler = (value: string) => {
|
||||
const markdown = HTMLToMarkdown.turndown(value);
|
||||
const backendFormat = getBackendFormat(markdown);
|
||||
setEditorValue(markdown);
|
||||
onTextChange && onTextChange(backendFormat);
|
||||
};
|
||||
const onChangeHandler = (value: string) => {
|
||||
const markdown = HTMLToMarkdown.turndown(value);
|
||||
const backendFormat = getBackendFormat(markdown);
|
||||
setEditorValue(markdown);
|
||||
onTextChange && onTextChange(backendFormat);
|
||||
};
|
||||
|
||||
const onSaveHandler = () => {
|
||||
if (editorRef.current) {
|
||||
if (editorRef.current?.getEditorValue()) {
|
||||
setEditorValue('');
|
||||
editorRef.current?.clearEditorValue();
|
||||
const message = getBackendFormat(editorRef.current?.getEditorValue());
|
||||
onSave && onSave(message);
|
||||
const onSaveHandler = () => {
|
||||
if (editorRef.current) {
|
||||
if (editorRef.current?.getEditorValue()) {
|
||||
setEditorValue('');
|
||||
editorRef.current?.clearEditorValue();
|
||||
const message = getBackendFormat(editorRef.current?.getEditorValue());
|
||||
onSave && onSave(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('relative', className)}
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
<FeedEditor
|
||||
defaultValue={defaultValue}
|
||||
editorClass={editorClass}
|
||||
focused={focused}
|
||||
placeHolder={placeHolder}
|
||||
ref={editorRef}
|
||||
onChangeHandler={onChangeHandler}
|
||||
onSave={onSaveHandler}
|
||||
/>
|
||||
{editAction ? (
|
||||
editAction
|
||||
) : (
|
||||
<>
|
||||
<SendButton editorValue={editorValue} onSaveHandler={onSaveHandler} />
|
||||
<KeyHelp editorValue={editorValue} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
/**
|
||||
* Handle forward ref logic and provide method access to parent component
|
||||
*/
|
||||
useImperativeHandle(ref, () => ({
|
||||
...editorRef.current,
|
||||
}));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('relative', className)}
|
||||
onClick={(e) => e.stopPropagation()}>
|
||||
<FeedEditor
|
||||
defaultValue={defaultValue}
|
||||
editorClass={editorClass}
|
||||
focused={focused}
|
||||
placeHolder={placeHolder}
|
||||
ref={editorRef}
|
||||
onChangeHandler={onChangeHandler}
|
||||
onSave={onSaveHandler}
|
||||
/>
|
||||
{editAction ?? (
|
||||
<>
|
||||
<SendButton
|
||||
editorValue={editorValue}
|
||||
onSaveHandler={onSaveHandler}
|
||||
/>
|
||||
<KeyHelp editorValue={editorValue} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default ActivityFeedEditor;
|
||||
|
||||
@ -420,6 +420,7 @@ export const ActivityFeedTab = ({
|
||||
'font-medium': taskFilter === 'open',
|
||||
}
|
||||
)}
|
||||
data-testid="open-task"
|
||||
onClick={() => {
|
||||
handleUpdateTaskFilter('open');
|
||||
setActiveThread();
|
||||
|
||||
@ -15,7 +15,14 @@ import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { EntityType } from '../../../../enums/entity.enum';
|
||||
import { TASK_COLUMNS, TASK_FEED } from '../../../../mocks/Task.mock';
|
||||
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';
|
||||
@ -39,7 +46,12 @@ jest.mock('../../../ActivityFeed/ActivityFeedCardV2/ActivityFeedCardV2', () => {
|
||||
});
|
||||
|
||||
jest.mock('../../../ActivityFeed/ActivityFeedEditor/ActivityFeedEditor', () => {
|
||||
return jest.fn().mockImplementation(() => <p>ActivityFeedEditor</p>);
|
||||
return jest.fn().mockImplementation(({ editAction }) => (
|
||||
<div>
|
||||
<p>ActivityFeedEditor</p>
|
||||
{editAction}
|
||||
</div>
|
||||
));
|
||||
});
|
||||
|
||||
jest.mock('../../../common/AssigneeList/AssigneeList', () => {
|
||||
@ -133,11 +145,7 @@ jest.mock(
|
||||
);
|
||||
|
||||
jest.mock('../../../../hooks/authHooks', () => ({
|
||||
useAuth: () => {
|
||||
return {
|
||||
isAdminUser: false,
|
||||
};
|
||||
},
|
||||
useAuth: jest.fn().mockReturnValue({ isAdminUser: false }),
|
||||
}));
|
||||
|
||||
const mockOnAfterClose = jest.fn();
|
||||
@ -168,6 +176,108 @@ describe('Test TaskFeedCard component', () => {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('task-cta-buttons')).toBeEmptyDOMElement();
|
||||
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(
|
||||
<TaskTab
|
||||
{...mockProps}
|
||||
taskThread={{
|
||||
...TASK_FEED,
|
||||
createdBy: 'xyz',
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
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(<TaskTab {...mockProps} />, {
|
||||
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(
|
||||
<TaskTab
|
||||
{...mockProps}
|
||||
taskThread={{ ...TASK_FEED, createdBy: 'xyz' }}
|
||||
/>,
|
||||
{
|
||||
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(
|
||||
<TaskTab
|
||||
{...mockProps}
|
||||
taskThread={{ ...TASK_FEED, createdBy: 'xyz', task: MOCK_TASK }}
|
||||
/>,
|
||||
{
|
||||
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(
|
||||
<TaskTab
|
||||
{...mockProps}
|
||||
taskThread={{ ...TASK_FEED, createdBy: 'xyz', task: MOCK_TASK_2 }}
|
||||
/>,
|
||||
{
|
||||
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(
|
||||
<TaskTab
|
||||
{...mockProps}
|
||||
taskThread={{ ...TASK_FEED, task: MOCK_TASK_3 }}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByTestId('glossary-accept-reject-task-dropdown')
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText('label.approve')).toBeInTheDocument();
|
||||
expect(screen.getByText('label.comment')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@ -49,6 +49,8 @@ import { useHistory } 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 } from '../../../../constants/constants';
|
||||
import { TaskOperation } from '../../../../constants/Feeds.constants';
|
||||
import { TASK_TYPES } from '../../../../constants/Task.constant';
|
||||
@ -88,21 +90,25 @@ 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 ActivityFeedEditor, {
|
||||
EditorContentRef,
|
||||
} from '../../../ActivityFeed/ActivityFeedEditor/ActivityFeedEditor';
|
||||
import { useActivityFeedProvider } from '../../../ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
|
||||
import AssigneeList from '../../../common/AssigneeList/AssigneeList';
|
||||
import InlineEdit from '../../../common/InlineEdit/InlineEdit.component';
|
||||
import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component';
|
||||
import EntityPopOverCard from '../../../common/PopOverCard/EntityPopOverCard';
|
||||
import RichTextEditor from '../../../common/RichTextEditor/RichTextEditor';
|
||||
import { EditorContentRef } from '../../../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor.interface';
|
||||
import { EditorContentRef as MarkdownEditorContentRef } from '../../../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor.interface';
|
||||
import TaskTabIncidentManagerHeader from '../TaskTabIncidentManagerHeader/TaskTabIncidentManagerHeader.component';
|
||||
import './task-tab.less';
|
||||
import { TaskTabProps } from './TaskTab.interface';
|
||||
@ -114,10 +120,11 @@ export const TaskTab = ({
|
||||
hasGlossaryReviewer,
|
||||
...rest
|
||||
}: TaskTabProps) => {
|
||||
const editorRef = useRef<EditorContentRef>();
|
||||
const history = useHistory();
|
||||
const [assigneesForm] = useForm();
|
||||
const { currentUser } = useApplicationStore();
|
||||
const markdownRef = useRef<EditorContentRef>();
|
||||
const markdownRef = useRef<MarkdownEditorContentRef>();
|
||||
const updatedAssignees = Form.useWatch('assignees', assigneesForm);
|
||||
const { permissions } = usePermissionProvider();
|
||||
const { task: taskDetails } = taskThread;
|
||||
@ -143,9 +150,56 @@ export const TaskTab = ({
|
||||
testCaseResolutionStatus,
|
||||
initialAssignees: usersList,
|
||||
} = 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);
|
||||
|
||||
@ -157,10 +211,20 @@ export const TaskTab = ({
|
||||
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];
|
||||
}
|
||||
}, [testCaseResolutionStatus, isTaskTestCaseResult]);
|
||||
}, [
|
||||
showAddSuggestionButton,
|
||||
testCaseResolutionStatus,
|
||||
isTaskGlossaryApproval,
|
||||
isTaskTestCaseResult,
|
||||
noSuggestionTaskMenuOptions,
|
||||
]);
|
||||
|
||||
const [taskAction, setTaskAction] = useState<TaskAction>(latestAction);
|
||||
const [isActionLoading, setIsActionLoading] = useState(false);
|
||||
@ -212,11 +276,12 @@ export const TaskTab = ({
|
||||
assignee.type === 'team' ? checkIfUserPartOfTeam(assignee.id) : false
|
||||
);
|
||||
|
||||
const isTaskDescription = isDescriptionTask(taskDetails?.type as TaskType);
|
||||
|
||||
const isTaskTags = isTagsTask(taskDetails?.type as TaskType);
|
||||
|
||||
const isTaskGlossaryApproval = taskDetails?.type === TaskType.RequestApproval;
|
||||
const getFormattedMenuOptions = (options: TaskAction[]) => {
|
||||
return options.map((item) => ({
|
||||
...item,
|
||||
icon: <Icon component={item.icon} style={{ fontSize: '16px' }} />,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleTaskLinkClick = () => {
|
||||
history.push({
|
||||
@ -341,16 +406,22 @@ export const TaskTab = ({
|
||||
(!hasGlossaryReviewer && isOwner) ||
|
||||
(Boolean(isPartOfAssigneeTeam) && !isCreator);
|
||||
|
||||
const onSave = (message: string) => {
|
||||
postFeed(message, taskThread?.id ?? '').catch(() => {
|
||||
// ignore since error is displayed in toast in the parent promise.
|
||||
// Added block for sonar code smell
|
||||
});
|
||||
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?.clearEditorValue();
|
||||
});
|
||||
};
|
||||
|
||||
const handleMenuItemClick: MenuProps['onClick'] = (info) => {
|
||||
if (info.key === TaskActionMode.EDIT) {
|
||||
setShowEditTaskModel(true);
|
||||
} else if (info.key === TaskActionMode.CLOSE) {
|
||||
onTaskReject();
|
||||
} else {
|
||||
onTaskResolve();
|
||||
}
|
||||
@ -458,7 +529,7 @@ export const TaskTab = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onTaskDropdownClick = () => {
|
||||
const onTestCaseTaskDropdownClick = () => {
|
||||
if (taskAction.key === TaskActionMode.RESOLVE) {
|
||||
setShowEditTaskModel(true);
|
||||
} else {
|
||||
@ -466,13 +537,54 @@ export const TaskTab = ({
|
||||
}
|
||||
};
|
||||
|
||||
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:
|
||||
onTaskReject();
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const onTaskDropdownClick = () => {
|
||||
if (
|
||||
taskAction.key === TaskActionMode.RESOLVE ||
|
||||
taskAction.key === TaskActionMode.EDIT
|
||||
) {
|
||||
handleMenuItemClick({ key: taskAction.key } as MenuInfo);
|
||||
} else {
|
||||
onTaskReject();
|
||||
}
|
||||
};
|
||||
|
||||
const renderCommentButton = useMemo(() => {
|
||||
return (
|
||||
<Button
|
||||
data-testid="comment-button"
|
||||
disabled={isEmpty(comment)}
|
||||
type="primary"
|
||||
onClick={onSave}>
|
||||
{t('label.comment')}
|
||||
</Button>
|
||||
);
|
||||
}, [comment, onSave]);
|
||||
|
||||
const approvalWorkflowActions = useMemo(() => {
|
||||
const hasApprovalAccess =
|
||||
isAssignee || (Boolean(isPartOfAssigneeTeam) && !isCreator);
|
||||
|
||||
return (
|
||||
<Space
|
||||
className="m-t-sm items-end w-full"
|
||||
className="m-t-sm items-end w-full justify-end"
|
||||
data-testid="task-cta-buttons"
|
||||
size="small">
|
||||
<Tooltip
|
||||
@ -481,31 +593,35 @@ export const TaskTab = ({
|
||||
? t('message.only-reviewers-can-approve-or-reject')
|
||||
: ''
|
||||
}>
|
||||
<Button
|
||||
data-testid="reject-task"
|
||||
<Dropdown.Button
|
||||
className="task-action-button"
|
||||
data-testid="glossary-accept-reject-task-dropdown"
|
||||
disabled={!hasApprovalAccess}
|
||||
onClick={onTaskReject}>
|
||||
{t('label.reject')}
|
||||
</Button>
|
||||
icon={<DownOutlined />}
|
||||
menu={{
|
||||
items: getFormattedMenuOptions(GLOSSARY_TASK_ACTION_LIST),
|
||||
selectable: true,
|
||||
selectedKeys: [taskAction.key],
|
||||
onClick: handleGlossaryTaskMenuClick,
|
||||
}}
|
||||
overlayClassName="task-action-dropdown"
|
||||
onClick={onTaskDropdownClick}>
|
||||
{taskAction.label}
|
||||
</Dropdown.Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
title={
|
||||
!hasApprovalAccess
|
||||
? t('message.only-reviewers-can-approve-or-reject')
|
||||
: ''
|
||||
}>
|
||||
<Button
|
||||
data-testid="approve-task"
|
||||
disabled={!hasApprovalAccess}
|
||||
type="primary"
|
||||
onClick={onTaskResolve}>
|
||||
{t('label.approve')}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
{renderCommentButton}
|
||||
</Space>
|
||||
);
|
||||
}, [taskDetails, onTaskResolve, isAssignee, isPartOfAssigneeTeam]);
|
||||
}, [
|
||||
taskAction,
|
||||
isAssignee,
|
||||
isCreator,
|
||||
isPartOfAssigneeTeam,
|
||||
renderCommentButton,
|
||||
handleGlossaryTaskMenuClick,
|
||||
onTaskDropdownClick,
|
||||
]);
|
||||
|
||||
const testCaseResultFlow = useMemo(() => {
|
||||
const editPermission = checkPermission(
|
||||
@ -516,32 +632,34 @@ export const TaskTab = ({
|
||||
const hasApprovalAccess = isAssignee || isCreator || editPermission;
|
||||
|
||||
return (
|
||||
<Dropdown.Button
|
||||
className="m-t-sm"
|
||||
data-testid="task-cta-buttons"
|
||||
icon={<DownOutlined />}
|
||||
loading={isActionLoading}
|
||||
menu={{
|
||||
items: INCIDENT_TASK_ACTION_LIST,
|
||||
selectable: true,
|
||||
selectedKeys: [taskAction.key],
|
||||
onClick: handleTaskMenuClick,
|
||||
disabled: !hasApprovalAccess,
|
||||
}}
|
||||
type="primary"
|
||||
onClick={onTaskDropdownClick}>
|
||||
{taskAction.label}
|
||||
</Dropdown.Button>
|
||||
<div className="m-t-sm d-flex justify-end items-center gap-4">
|
||||
<Dropdown.Button
|
||||
className="w-auto task-action-button"
|
||||
data-testid="task-cta-buttons"
|
||||
icon={<DownOutlined />}
|
||||
loading={isActionLoading}
|
||||
menu={{
|
||||
items: INCIDENT_TASK_ACTION_LIST,
|
||||
selectable: true,
|
||||
selectedKeys: [taskAction.key],
|
||||
onClick: handleTaskMenuClick,
|
||||
disabled: !hasApprovalAccess,
|
||||
}}
|
||||
onClick={onTestCaseTaskDropdownClick}>
|
||||
{taskAction.label}
|
||||
</Dropdown.Button>
|
||||
{renderCommentButton}
|
||||
</div>
|
||||
);
|
||||
}, [taskDetails, isAssignee, isPartOfAssigneeTeam, taskAction]);
|
||||
}, [
|
||||
taskDetails,
|
||||
isAssignee,
|
||||
isPartOfAssigneeTeam,
|
||||
taskAction,
|
||||
renderCommentButton,
|
||||
]);
|
||||
|
||||
const actionButtons = useMemo(() => {
|
||||
if (isTaskClosed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const taskType = taskDetails?.type ?? '';
|
||||
|
||||
if (isTaskGlossaryApproval) {
|
||||
return approvalWorkflowActions;
|
||||
}
|
||||
@ -550,49 +668,47 @@ export const TaskTab = ({
|
||||
return testCaseResultFlow;
|
||||
}
|
||||
|
||||
const parsedSuggestion = [
|
||||
'RequestDescription',
|
||||
'UpdateDescription',
|
||||
].includes(taskType)
|
||||
? taskDetails?.suggestion
|
||||
: JSON.parse(taskDetails?.suggestion || '[]');
|
||||
|
||||
return (
|
||||
<Space
|
||||
className="m-t-sm items-end w-full"
|
||||
className="m-t-sm items-end w-full justify-end"
|
||||
data-testid="task-cta-buttons"
|
||||
size="small">
|
||||
{(isCreator || hasEditAccess) && (
|
||||
<Button onClick={onTaskReject}>{t('label.close')}</Button>
|
||||
{isCreator && !hasEditAccess && (
|
||||
<Button data-testid="close-button" onClick={onTaskReject}>
|
||||
{t('label.close')}
|
||||
</Button>
|
||||
)}
|
||||
{hasEditAccess ? (
|
||||
{hasEditAccess && (
|
||||
<>
|
||||
{['RequestDescription', 'RequestTag'].includes(taskType) &&
|
||||
isEmpty(parsedSuggestion) ? (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() =>
|
||||
handleMenuItemClick({ key: TaskActionMode.EDIT } as MenuInfo)
|
||||
}>
|
||||
{taskThread.task?.newValue
|
||||
? t('label.add-suggestion')
|
||||
: t('label.add-entity', {
|
||||
entity: isTaskTags
|
||||
? t('label.tag-plural')
|
||||
: t('label.description'),
|
||||
})}
|
||||
</Button>
|
||||
{showAddSuggestionButton ? (
|
||||
<div className="d-flex justify-end gap-2">
|
||||
<Dropdown.Button
|
||||
className="task-action-button"
|
||||
data-testid="add-close-task-dropdown"
|
||||
icon={<DownOutlined />}
|
||||
menu={{
|
||||
items: getFormattedMenuOptions(noSuggestionTaskMenuOptions),
|
||||
selectable: true,
|
||||
selectedKeys: [taskAction.key],
|
||||
onClick: handleMenuItemClick,
|
||||
}}
|
||||
overlayClassName="task-action-dropdown"
|
||||
onClick={onTaskDropdownClick}>
|
||||
{taskAction.label}
|
||||
</Dropdown.Button>
|
||||
</div>
|
||||
) : (
|
||||
<Dropdown.Button
|
||||
className="task-action-button"
|
||||
data-testid="edit-accept-task-dropdown"
|
||||
icon={<DownOutlined />}
|
||||
menu={{
|
||||
items: TASK_ACTION_LIST,
|
||||
items: getFormattedMenuOptions(TASK_ACTION_LIST),
|
||||
selectable: true,
|
||||
selectedKeys: [taskAction.key],
|
||||
onClick: handleMenuItemClick,
|
||||
}}
|
||||
type="primary"
|
||||
overlayClassName="task-action-dropdown"
|
||||
onClick={() =>
|
||||
taskAction.key === TaskActionMode.EDIT
|
||||
? handleMenuItemClick({ key: taskAction.key } as MenuInfo)
|
||||
@ -602,22 +718,24 @@ export const TaskTab = ({
|
||||
</Dropdown.Button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{renderCommentButton}
|
||||
</Space>
|
||||
);
|
||||
}, [
|
||||
onTaskReject,
|
||||
taskDetails,
|
||||
onTaskResolve,
|
||||
handleMenuItemClick,
|
||||
taskAction,
|
||||
isTaskClosed,
|
||||
isTaskGlossaryApproval,
|
||||
showAddSuggestionButton,
|
||||
isCreator,
|
||||
approvalWorkflowActions,
|
||||
testCaseResultFlow,
|
||||
isTaskTestCaseResult,
|
||||
renderCommentButton,
|
||||
]);
|
||||
|
||||
const initialFormValue = useMemo(() => {
|
||||
@ -806,10 +924,13 @@ export const TaskTab = ({
|
||||
|
||||
<Col span={24}>
|
||||
{taskDetails?.status === ThreadTaskStatus.Open && (
|
||||
<ActivityFeedEditor onSave={onSave} onTextChange={setComment} />
|
||||
<ActivityFeedEditor
|
||||
editAction={actionButtons}
|
||||
ref={editorRef}
|
||||
onSave={onSave}
|
||||
onTextChange={setComment}
|
||||
/>
|
||||
)}
|
||||
|
||||
{actionButtons}
|
||||
</Col>
|
||||
{isTaskTestCaseResult ? (
|
||||
<Modal
|
||||
|
||||
@ -18,3 +18,33 @@
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.task-action-button {
|
||||
button {
|
||||
border: 1px solid @primary-color;
|
||||
color: @primary-color;
|
||||
|
||||
&:first-child {
|
||||
border-right: none;
|
||||
padding-right: 0;
|
||||
}
|
||||
&:last-child {
|
||||
border-left: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.task-action-dropdown {
|
||||
ul {
|
||||
padding: 4px 8px;
|
||||
|
||||
li {
|
||||
padding: 8px;
|
||||
border-bottom: @global-border;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -285,3 +285,53 @@ export const MOCK_TASK_ASSIGNEE = [
|
||||
value: 'id1',
|
||||
},
|
||||
];
|
||||
|
||||
export const MOCK_TASK = {
|
||||
id: 1,
|
||||
type: TaskType.RequestTag,
|
||||
assignees: [
|
||||
{
|
||||
id: 'd6764107-e8b4-4748-b256-c86fecc66064',
|
||||
type: 'User',
|
||||
name: 'xyz',
|
||||
fullyQualifiedName: 'xyz',
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
status: ThreadTaskStatus.Open,
|
||||
oldValue: '[]',
|
||||
suggestion:
|
||||
'[{"tagFQN":"PersonalData.SpecialCategory","source":"Classification","name":"SpecialCategory","description":"GDPR special category data is personal information of data subjects that is especially sensitive, the exposure of which could significantly impact the rights and freedoms of data subjects and potentially be used against them for unlawful discrimination."}]',
|
||||
};
|
||||
|
||||
export const MOCK_TASK_2 = {
|
||||
id: 1,
|
||||
type: TaskType.RequestTag,
|
||||
assignees: [
|
||||
{
|
||||
id: 'd6764107-e8b4-4748-b256-c86fecc66064',
|
||||
type: 'User',
|
||||
name: 'xyz',
|
||||
fullyQualifiedName: 'xyz',
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
status: ThreadTaskStatus.Open,
|
||||
oldValue: '[]',
|
||||
};
|
||||
|
||||
export const MOCK_TASK_3 = {
|
||||
id: 1,
|
||||
type: TaskType.RequestApproval,
|
||||
assignees: [
|
||||
{
|
||||
id: 'd6764107-e8b4-4748-b256-c86fecc66064',
|
||||
type: 'User',
|
||||
name: 'xyz',
|
||||
fullyQualifiedName: 'xyz',
|
||||
deleted: false,
|
||||
},
|
||||
],
|
||||
status: ThreadTaskStatus.Open,
|
||||
oldValue: '[]',
|
||||
};
|
||||
|
||||
@ -55,6 +55,7 @@ export interface Option {
|
||||
export interface TaskAction {
|
||||
label: string;
|
||||
key: string;
|
||||
icon?: SvgComponent;
|
||||
}
|
||||
|
||||
export enum TaskActionMode {
|
||||
@ -62,6 +63,7 @@ export enum TaskActionMode {
|
||||
EDIT = 'edit',
|
||||
RE_ASSIGN = 're-assign',
|
||||
RESOLVE = 'resolve',
|
||||
CLOSE = 'close',
|
||||
}
|
||||
|
||||
export enum TaskTabs {
|
||||
|
||||
@ -10,11 +10,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { AxiosError } from 'axios';
|
||||
import { Change, diffWordsWithSpace } from 'diff';
|
||||
import i18Next from 'i18next';
|
||||
import { isEmpty, isEqual, isUndefined } from 'lodash';
|
||||
import React from 'react';
|
||||
import { ReactComponent as CancelColored } from '../assets/svg/cancel-colored.svg';
|
||||
import { ReactComponent as EditColored } from '../assets/svg/edit-colored.svg';
|
||||
import { ReactComponent as SuccessColored } from '../assets/svg/success-colored.svg';
|
||||
import { ActivityFeedTabs } from '../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
|
||||
import {
|
||||
getEntityDetailsPath,
|
||||
@ -595,14 +598,38 @@ export const fetchEntityDetail = (
|
||||
}
|
||||
};
|
||||
|
||||
export const TASK_ACTION_COMMON_ITEM: TaskAction[] = [
|
||||
{
|
||||
label: i18Next.t('label.close'),
|
||||
key: TaskActionMode.CLOSE,
|
||||
icon: CancelColored,
|
||||
},
|
||||
];
|
||||
|
||||
export const TASK_ACTION_LIST: TaskAction[] = [
|
||||
{
|
||||
label: i18Next.t('label.accept-suggestion'),
|
||||
key: TaskActionMode.VIEW,
|
||||
icon: SuccessColored,
|
||||
},
|
||||
{
|
||||
label: i18Next.t('label.edit-amp-accept-suggestion'),
|
||||
key: TaskActionMode.EDIT,
|
||||
icon: EditColored,
|
||||
},
|
||||
...TASK_ACTION_COMMON_ITEM,
|
||||
];
|
||||
|
||||
export const GLOSSARY_TASK_ACTION_LIST: TaskAction[] = [
|
||||
{
|
||||
label: i18Next.t('label.approve'),
|
||||
key: TaskActionMode.RESOLVE,
|
||||
icon: SuccessColored,
|
||||
},
|
||||
{
|
||||
label: i18Next.t('label.reject'),
|
||||
key: TaskActionMode.CLOSE,
|
||||
icon: CancelColored,
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user