#20914: supported bulk edit in glossary page (#20993)

* glossary import optimization around files under BulkImportPage

* fix unit test

* fix the special character glossary breaking for import

* supported bulk edit in glossary page

* added playwright test
This commit is contained in:
Ashish Gupta 2025-04-29 15:00:37 +05:30 committed by GitHub
parent 6f5a3afac5
commit 96e07e7ce0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 145 additions and 5 deletions

View File

@ -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();
});
});

View File

@ -301,7 +301,7 @@ export const fillGlossaryRowDetails = async (
owners: string[];
},
page: Page,
propertyListName: Record<string, string>
propertyListName?: Record<string, string>
) => {
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 (

View File

@ -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<HTMLDivElement>(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) => {
</Space>
</Button>
</Dropdown>
{getBulkEditButton(permissions.EditAll, handleEditGlossary)}
<Button
className="text-primary remove-button-background-hover"
data-testid="expand-collapse-all-button"

View File

@ -498,7 +498,7 @@ const BulkEntityImportPage = () => {
activeAsyncImportJob={activeAsyncImportJob}
activeStep={activeStep}
breadcrumbList={breadcrumbList}
columns={columns}
columns={filterColumns}
dataSource={dataSource}
handleBack={handleBack}
handleValidate={handleValidate}

View File

@ -20,6 +20,7 @@ import {
exportDatabaseDetailsInCSV,
exportDatabaseSchemaDetailsInCSV,
} from '../../rest/databaseAPI';
import { exportGlossaryInCSVFormat } from '../../rest/glossaryAPI';
import { exportDatabaseServiceDetailsInCSV } from '../../rest/serviceAPI';
import { exportTableDetailsInCSV } from '../../rest/tableAPI';
import i18n from '../i18next/LocalUtil';
@ -39,6 +40,9 @@ export const getBulkEditCSVExportEntityApi = (entityType: EntityType) => {
case EntityType.DATABASE_SCHEMA:
return exportDatabaseSchemaDetailsInCSV;
case EntityType.GLOSSARY_TERM:
return exportGlossaryInCSVFormat;
case EntityType.TABLE:
return exportTableDetailsInCSV;