diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts index 338a00843ec..1ffc60342bd 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Features/BulkEditEntity.spec.ts @@ -16,6 +16,8 @@ import { SERVICE_TYPE } from '../../constant/service'; import { GlobalSettingOptions } from '../../constant/settings'; import { EntityDataClass } from '../../support/entity/EntityDataClass'; import { TableClass } from '../../support/entity/TableClass'; +import { Glossary } from '../../support/glossary/Glossary'; +import { GlossaryTerm } from '../../support/glossary/GlossaryTerm'; import { createNewPage, descriptionBoxReadOnly, @@ -23,13 +25,16 @@ import { redirectToHomePage, toastNotification, } from '../../utils/common'; +import { selectActiveGlossaryTerm } from '../../utils/glossary'; import { createColumnRowDetails, createCustomPropertiesForEntity, createDatabaseRowDetails, createDatabaseSchemaRowDetails, + createGlossaryTermRowDetails, createTableRowDetails, fillDescriptionDetails, + fillGlossaryRowDetails, fillGlossaryTermDetails, fillRowDetails, fillTagDetails, @@ -611,4 +616,116 @@ test.describe('Bulk Edit Entity', () => { await tableEntity.delete(apiContext); await afterAction(); }); + + test('Glossary', async ({ page }) => { + test.slow(); + + const additionalGlossaryTerm = createGlossaryTermRowDetails(); + const glossary = new Glossary(); + const glossaryTerm = new GlossaryTerm(glossary); + + const { apiContext, afterAction } = await getApiContext(page); + await glossary.create(apiContext); + await glossaryTerm.create(apiContext); + + await test.step('Perform bulk edit action', async () => { + await glossary.visitEntityPage(page); + + await page.click('[data-testid="bulk-edit-table"]'); + + // Adding manual wait for the file to load + await page.waitForTimeout(500); + + // Adding some assertion to make sure that CSV loaded correctly + await expect( + page.locator('.InovuaReactDataGrid__header-layout') + ).toBeVisible(); + await expect(page.getByRole('button', { name: 'Next' })).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Previous' }) + ).not.toBeVisible(); + + // Click on first cell and edit + await page.click( + '.InovuaReactDataGrid__row--first > .InovuaReactDataGrid__row-cell-wrap > .InovuaReactDataGrid__cell--first' + ); + + // Click on first cell and edit + await fillGlossaryRowDetails( + { + ...additionalGlossaryTerm, + name: glossaryTerm.data.name, + owners: [EntityDataClass.user1.responseData?.['displayName']], + reviewers: [EntityDataClass.user2.responseData?.['displayName']], + relatedTerm: { + parent: glossary.data.name, + name: glossaryTerm.data.name, + }, + }, + page + ); + + await page.getByRole('button', { name: 'Next' }).click(); + const loader = page.locator( + '.inovua-react-toolkit-load-mask__background-layer' + ); + + await loader.waitFor({ state: 'hidden' }); + + await validateImportStatus(page, { + passed: '2', + processed: '2', + failed: '0', + }); + + await page.waitForSelector('.InovuaReactDataGrid__header-layout', { + state: 'visible', + }); + + const rowStatus = ['Entity updated']; + + await expect(page.locator('[data-props-id="details"]')).toHaveText( + rowStatus + ); + + await page.getByRole('button', { name: 'Update' }).click(); + await page + .locator('.inovua-react-toolkit-load-mask__background-layer') + .waitFor({ state: 'detached' }); + + await toastNotification( + page, + `Glossaryterm ${glossary.responseData.fullyQualifiedName} details updated successfully` + ); + + await selectActiveGlossaryTerm(page, additionalGlossaryTerm.displayName); + + // Verify Description + await expect(page.getByText('Playwright GlossaryTerm')).toBeVisible(); + + // Verify Synonyms + await expect( + page.getByTestId('playwright,glossaryTerm,testing') + ).toBeVisible(); + + // Verify References + await expect(page.getByTestId('reference-link-data')).toBeVisible(); + + // Verify Tags + await expect(page.getByTestId('tag-PII.Sensitive')).toBeVisible(); + + // Verify Owners + await expect( + page.getByTestId(EntityDataClass.user1.responseData?.['displayName']) + ).toBeVisible(); + + // Verify Reviewers + await expect( + page.getByTestId(EntityDataClass.user2.responseData?.['displayName']) + ).toBeVisible(); + }); + + await glossary.delete(apiContext); + await afterAction(); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/importUtils.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/importUtils.ts index adba35a88a9..bc42df9ef5f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/importUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/importUtils.ts @@ -301,7 +301,7 @@ export const fillGlossaryRowDetails = async ( owners: string[]; }, page: Page, - propertyListName: Record + propertyListName?: Record ) => { await page .locator('.InovuaReactDataGrid__cell--cell-active') @@ -362,7 +362,9 @@ export const fillGlossaryRowDetails = async ( .locator('.InovuaReactDataGrid__cell--cell-active') .press('ArrowRight', { delay: 100 }); - await fillCustomPropertyDetails(page, propertyListName); + if (propertyListName) { + await fillCustomPropertyDetails(page, propertyListName); + } }; export const validateImportStatus = async ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx index 8693cab331c..0f58391ede3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTermTab/GlossaryTermTab.component.tsx @@ -39,7 +39,7 @@ import React, { import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; +import { Link, useHistory } from 'react-router-dom'; import { ReactComponent as IconDrag } from '../../../assets/svg/drag.svg'; import { ReactComponent as EditIcon } from '../../../assets/svg/edit-new.svg'; import { ReactComponent as IconDown } from '../../../assets/svg/ic-arrow-down.svg'; @@ -88,7 +88,11 @@ import { patchGlossaryTerm, } from '../../../rest/glossaryAPI'; import { Transi18next } from '../../../utils/CommonUtils'; -import { getEntityName } from '../../../utils/EntityUtils'; +import { getBulkEditButton } from '../../../utils/EntityBulkEdit/EntityBulkEditUtils'; +import { + getEntityBulkEditPath, + getEntityName, +} from '../../../utils/EntityUtils'; import Fqn from '../../../utils/Fqn'; import { buildTree, @@ -116,6 +120,7 @@ import { } from './GlossaryTermTab.interface'; const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { + const history = useHistory(); const { currentUser } = useApplicationStore(); const tableContainerRef = useRef(null); const [containerWidth, setContainerWidth] = useState(0); @@ -638,6 +643,15 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { ] ); + const handleEditGlossary = () => { + history.push({ + pathname: getEntityBulkEditPath( + EntityType.GLOSSARY_TERM, + activeGlossary?.fullyQualifiedName ?? '' + ), + }); + }; + const extraTableFilters = useMemo(() => { return ( <> @@ -658,6 +672,9 @@ const GlossaryTermTab = ({ isGlossary, className }: GlossaryTermTabProps) => { + + {getBulkEditButton(permissions.EditAll, handleEditGlossary)} +