diff --git a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/tagAPI.ts b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/tagAPI.ts index 6b2eb9c619b..d379d7e7313 100644 --- a/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/tagAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/axiosAPIs/tagAPI.ts @@ -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 - >(`/tags/${name}`, data); +export const updateTagCategory = async (name: string, data: TagCategory) => { + const response = await APIClient.put>( + `/tags/${name}`, + data + ); return response.data; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/tags/index.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/tags/index.test.tsx index 7577916f505..c42e090bd44 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/tags/index.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/tags/index.test.tsx @@ -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(); + }); + 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(); + }); + 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(() => diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/tags/index.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/tags/index.tsx index eb9ea1ca01c..e47d4b3690c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/tags/index.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/tags/index.tsx @@ -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(DEFAULT_ENTITY_PERMISSION); + const [isNameEditing, setIsNameEditing] = useState(false); + const [currentCategoryName, setCurrentCategoryName] = useState(''); 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) => { + setCurrentCategoryName(e.target.value); + }; + const onNewTagChange = (data: TagCategory, forceSet = false) => { if (errorDataTag || forceSet) { const errData: { [key: string]: string } = {}; @@ -632,14 +673,84 @@ const TagsPage = () => { ) : (
{currentCategory && ( -
-
- {currentCategory.displayName ?? currentCategory.name} -
+ + + {isNameEditing ? ( + + + + + + + + )} + + )} +
{ Delete category
-
+ )}
{ isEdit={isEditCategory} onCancel={() => setIsEditCategory(false)} onDescriptionEdit={() => setIsEditCategory(true)} - onDescriptionUpdate={UpdateCategory} + onDescriptionUpdate={handleUpdateDescription} />
{ return entity?.displayName || entity?.name || ''; };