UI: Added support for renaming user generated tag category (#9154)

* UI: Added support for renaming user generated tag category

* fixed failing cypress for tags

* Addressing comments and added unit test
This commit is contained in:
Shailesh Parmar 2022-12-06 15:26:59 +05:30 committed by GitHub
parent d2b61d6065
commit d2fdb47f22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 197 additions and 24 deletions

View File

@ -50,11 +50,11 @@ export const createTagCategory = async (data: TagsCategory) => {
return response.data;
};
export const updateTagCategory = async (name: string, data: TagsCategory) => {
const response = await APIClient.put<
TagsCategory,
AxiosResponse<TagCategory>
>(`/tags/${name}`, data);
export const updateTagCategory = async (name: string, data: TagCategory) => {
const response = await APIClient.put<TagCategory, AxiosResponse<TagCategory>>(
`/tags/${name}`,
data
);
return response.data;
};

View File

@ -45,7 +45,9 @@ jest.mock('../../authentication/auth-provider/AuthProvider', () => {
});
jest.mock('react-router-dom', () => ({
useHistory: jest.fn(),
useHistory: jest.fn().mockImplementation(() => ({
push: jest.fn(),
})),
useParams: jest.fn().mockReturnValue({
entityTypeFQN: 'entityTypeFQN',
}),
@ -136,6 +138,7 @@ const mockCategory = [
version: 0.1,
updatedAt: 1649665563410,
updatedBy: 'admin',
provider: 'user',
href: 'http://localhost:8585/api/v1/tags/PII',
usageCount: 0,
children: [
@ -500,6 +503,63 @@ describe('Test TagsPage page', () => {
expect(sidePanelCategories.length).toBe(2);
});
it('System tag category should not be renamed', async () => {
await act(async () => {
render(<TagsPage />);
});
const tagsComponent = await screen.findByTestId('tags-container');
const header = await screen.findByTestId('header');
const editIcon = screen.queryByTestId('name-edit-icon');
expect(tagsComponent).toBeInTheDocument();
expect(header).toBeInTheDocument();
expect(editIcon).not.toBeInTheDocument();
});
it('User tag category should be renamed', async () => {
(getTagCategories as jest.Mock).mockImplementationOnce(() =>
Promise.resolve({ data: [mockCategory[1]] })
);
await act(async () => {
render(<TagsPage />);
});
const tagsComponent = await screen.findByTestId('tags-container');
const header = await screen.findByTestId('header');
const leftPanelContent = await screen.findByTestId('left-panel-content');
const editIcon = await screen.findByTestId('name-edit-icon');
const tagCategoryName = await screen.findByTestId('category-name');
expect(tagsComponent).toBeInTheDocument();
expect(header).toBeInTheDocument();
expect(leftPanelContent).toBeInTheDocument();
expect(editIcon).toBeInTheDocument();
expect(tagCategoryName).toBeInTheDocument();
await act(async () => {
fireEvent.click(editIcon);
});
const tagCategoryHeading = await screen.findByTestId('tag-category-name');
const cancelAssociatedTag = await screen.findByTestId(
'cancelAssociatedTag'
);
const saveAssociatedTag = await screen.findByTestId('saveAssociatedTag');
expect(tagCategoryHeading).toBeInTheDocument();
expect(cancelAssociatedTag).toBeInTheDocument();
expect(saveAssociatedTag).toBeInTheDocument();
await act(async () => {
fireEvent.change(tagCategoryHeading, {
target: {
value: 'newPII',
},
});
});
expect(tagCategoryHeading).toHaveValue('newPII');
});
describe('Render Sad Paths', () => {
it('Show error message on failing of deleteTagCategory API', async () => {
(deleteTagCategory as jest.Mock).mockImplementation(() =>

View File

@ -12,7 +12,16 @@
*/
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, Table, Tooltip, Typography } from 'antd';
import {
Button,
Col,
Input,
Row,
Space,
Table,
Tooltip,
Typography,
} from 'antd';
import { ColumnsType } from 'antd/lib/table';
import { AxiosError } from 'axios';
import { t } from 'i18next';
@ -108,6 +117,8 @@ const TagsPage = () => {
});
const [categoryPermissions, setCategoryPermissions] =
useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION);
const [isNameEditing, setIsNameEditing] = useState<boolean>(false);
const [currentCategoryName, setCurrentCategoryName] = useState<string>('');
const createCategoryPermission = useMemo(
() =>
@ -136,6 +147,11 @@ const TagsPage = () => {
}
};
const handleEditNameCancel = () => {
setIsNameEditing(false);
setCurrentCategoryName(currentCategory?.name || '');
};
const fetchCategories = (setCurrent?: boolean) => {
setIsLoading(true);
getTagCategories('usageCount')
@ -144,6 +160,7 @@ const TagsPage = () => {
setCategoreis(res.data);
if (setCurrent) {
setCurrentCategory(res.data[0]);
setCurrentCategoryName(res.data[0].name);
}
} else {
throw jsonData['api-error-messages']['unexpected-server-response'];
@ -168,7 +185,8 @@ const TagsPage = () => {
try {
const currentCategory = await getCategory(name, 'usageCount');
if (currentCategory) {
setCurrentCategory(currentCategory as TagCategory);
setCurrentCategory(currentCategory);
setCurrentCategoryName(currentCategory.name);
setIsLoading(false);
} else {
showErrorToast(
@ -328,14 +346,19 @@ const TagsPage = () => {
}
};
const UpdateCategory = async (updatedHTML: string) => {
const handleUpdateCategory = async (updatedCategory: TagCategory) => {
try {
const response = await updateTagCategory(currentCategory?.name ?? '', {
name: currentCategory?.name ?? '',
description: updatedHTML,
});
const response = await updateTagCategory(
currentCategory?.name ?? '',
updatedCategory
);
if (response) {
await fetchCurrentCategory(currentCategory?.name as string, true);
if (currentCategory?.name !== updatedCategory.name) {
history.push(getTagPath(response.name));
setIsNameEditing(false);
} else {
await fetchCurrentCategory(currentCategory?.name as string, true);
}
} else {
throw jsonData['api-error-messages']['unexpected-server-response'];
}
@ -346,6 +369,24 @@ const TagsPage = () => {
}
};
const handleRenameSave = () => {
handleUpdateCategory({
name: (currentCategoryName || currentCategory?.name) ?? '',
description: currentCategory?.description ?? '',
});
};
const handleUpdateDescription = async (updatedHTML: string) => {
handleUpdateCategory({
name: currentCategory?.name ?? '',
description: updatedHTML,
});
};
const handleCategoryNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setCurrentCategoryName(e.target.value);
};
const onNewTagChange = (data: TagCategory, forceSet = false) => {
if (errorDataTag || forceSet) {
const errData: { [key: string]: string } = {};
@ -632,14 +673,84 @@ const TagsPage = () => {
) : (
<div className="full-height" data-testid="tags-container">
{currentCategory && (
<div
className="tw-flex tw-justify-between tw-items-center"
data-testid="header">
<div
className="tw-text-link tw-text-base tw-py-2"
data-testid="category-name">
{currentCategory.displayName ?? currentCategory.name}
</div>
<Space className="w-full justify-between" data-testid="header">
<Space className="items-center">
{isNameEditing ? (
<Row align="middle" gutter={8}>
<Col>
<Input
className="input-width"
data-testid="tag-category-name"
name="tagCategoryName"
value={currentCategoryName}
onChange={handleCategoryNameChange}
/>
</Col>
<Col>
<Button
className="icon-buttons"
data-testid="cancelAssociatedTag"
icon={
<FontAwesomeIcon
className="w-3.5 h-3.5"
icon="times"
/>
}
size="small"
type="primary"
onMouseDown={handleEditNameCancel}
/>
<Button
className="icon-buttons m-l-xss"
data-testid="saveAssociatedTag"
icon={
<FontAwesomeIcon
className="w-3.5 h-3.5"
icon="check"
/>
}
size="small"
type="primary"
onMouseDown={handleRenameSave}
/>
</Col>
</Row>
) : (
<Space>
<Typography.Title
className="m-b-0"
data-testid="category-name"
level={5}>
{getEntityName(currentCategory)}
</Typography.Title>
{currentCategory.provider === ProviderType.User && (
<Tooltip
title={
categoryPermissions.EditAll
? t('label.edit-entity', {
entity: t('label.name'),
})
: NO_PERMISSION_FOR_ACTION
}>
<Button
className="p-0"
data-testid="name-edit-icon"
disabled={!categoryPermissions.EditAll}
size="small"
type="text"
onClick={() => setIsNameEditing(true)}>
<SVGIcons
alt="icon-tag"
className="tw-mx-1"
icon={Icons.EDIT}
width="16"
/>
</Button>
</Tooltip>
)}
</Space>
)}
</Space>
<div className="flex-center">
<Tooltip
title={
@ -677,7 +788,7 @@ const TagsPage = () => {
Delete category
</Button>
</div>
</div>
</Space>
)}
<div className="m-b-sm" data-testid="description-container">
<Description
@ -692,7 +803,7 @@ const TagsPage = () => {
isEdit={isEditCategory}
onCancel={() => setIsEditCategory(false)}
onDescriptionEdit={() => setIsEditCategory(true)}
onDescriptionUpdate={UpdateCategory}
onDescriptionUpdate={handleUpdateDescription}
/>
</div>
<Table

View File

@ -78,6 +78,7 @@ import { Webhook } from '../generated/entity/events/webhook';
import { ThreadTaskStatus, ThreadType } from '../generated/entity/feed/thread';
import { Policy } from '../generated/entity/policies/policy';
import { PipelineType } from '../generated/entity/services/ingestionPipelines/ingestionPipeline';
import { TagCategory } from '../generated/entity/tags/tagCategory';
import { Role } from '../generated/entity/teams/role';
import { Team } from '../generated/entity/teams/team';
import { EntityReference, User } from '../generated/entity/teams/user';
@ -668,6 +669,7 @@ export const getEntityName = (
| Webhook
| Bot
| Kpi
| TagCategory
) => {
return entity?.displayName || entity?.name || '';
};