fix: edit description permission for domain owner (#19475)

* fix: edit description permission for domain owner

* fix: usage of permissions prop

* test: fix unit test and add e2e test for edit description

* refactor: remove editCustomAttributePermission and viewAllPermission props

* fix: e2e test for edit description

* fix: playwright tests

* fix: update e2e test for edit description

(cherry picked from commit 4059df8843afe472f27b3712a2a7ef562f1f5f10)
This commit is contained in:
Pranita Fulsundar 2025-01-23 18:56:46 +05:30 committed by OpenMetadata Release Bot
parent 95501d850e
commit 4abe64087a
8 changed files with 194 additions and 70 deletions

View File

@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import test, { expect } from '@playwright/test';
import base, { expect, Page } from '@playwright/test';
import { Operation } from 'fast-json-patch';
import { get } from 'lodash';
import { SidebarItem } from '../../constant/sidebar';
@ -40,11 +40,53 @@ import {
import { sidebarClick } from '../../utils/sidebar';
import { performUserLogin, visitUserProfilePage } from '../../utils/user';
test.describe('Domains', () => {
test.use({ storageState: 'playwright/.auth/admin.json' });
const user = new UserClass();
const domain = new Domain();
const test = base.extend<{
page: Page;
userPage: Page;
}>({
page: async ({ browser }, use) => {
const { page } = await performAdminLogin(browser);
await use(page);
await page.close();
},
userPage: async ({ browser }, use) => {
const page = await browser.newPage();
await user.login(page);
await use(page);
await page.close();
},
});
test.describe('Domains', () => {
test.slow(true);
test.beforeAll('Setup pre-requests', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await user.create(apiContext);
await domain.create(apiContext);
await domain.patch({
apiContext,
patchData: [
{
op: 'add',
path: '/owners/0',
value: {
id: user.responseData.id,
type: 'user',
},
},
],
});
await afterAction();
});
test.beforeEach('Visit home page', async ({ page }) => {
await redirectToHomePage(page);
});
@ -389,6 +431,41 @@ test.describe('Domains', () => {
await afterAction();
}
});
test('Domain owner should able to edit description of domain', async ({
page,
userPage,
}) => {
const { afterAction, apiContext } = await getApiContext(page);
try {
await sidebarClick(userPage, SidebarItem.DOMAIN);
await selectDomain(userPage, domain.data);
await expect(userPage.getByTestId('edit-description')).toBeInViewport();
await userPage.getByTestId('edit-description').click();
await expect(userPage.getByTestId('editor')).toBeInViewport();
const descriptionInputBox = '.om-block-editor[contenteditable="true"]';
await userPage.fill(descriptionInputBox, 'test description');
await userPage.getByTestId('save').click();
await userPage.waitForTimeout(3000);
const descriptionBox = '.om-block-editor[contenteditable="false"]';
await expect(userPage.locator(descriptionBox)).toHaveText(
'test description'
);
} finally {
await domain?.delete(apiContext);
await user.delete(apiContext);
}
await afterAction();
});
});
test.describe('Domains Rbac', () => {

View File

@ -11,6 +11,7 @@
* limitations under the License.
*/
import { APIRequestContext } from '@playwright/test';
import { Operation } from 'fast-json-patch';
import { uuid } from '../../utils/common';
type UserTeamRef = {
@ -72,4 +73,28 @@ export class Domain {
return response.body;
}
async patch({
apiContext,
patchData,
}: {
apiContext: APIRequestContext;
patchData: Operation[];
}) {
const response = await apiContext.patch(
`/api/v1/domains/${this.responseData?.id}`,
{
data: patchData,
headers: {
'Content-Type': 'application/json-patch+json',
},
}
);
this.responseData = await response.json();
return {
entity: this.responseData,
};
}
}

View File

@ -11,6 +11,7 @@
* limitations under the License.
*/
import { APIRequestContext } from '@playwright/test';
import { Operation } from 'fast-json-patch';
import { uuid } from '../../utils/common';
import { Domain } from './Domain';
@ -76,4 +77,28 @@ export class SubDomain {
return response.body;
}
async patch({
apiContext,
patchData,
}: {
apiContext: APIRequestContext;
patchData: Operation[];
}) {
const response = await apiContext.patch(
`/api/v1/domains/${this.responseData?.id}`,
{
data: patchData,
headers: {
'Content-Type': 'application/json-patch+json',
},
}
);
this.responseData = await response.json();
return {
entity: this.responseData,
};
}
}

View File

@ -411,14 +411,9 @@ const DataProductsDetailsPage = ({
children: (
<DocumentationTab
domain={dataProduct}
editCustomAttributePermission={
(dataProductPermission.EditAll ||
dataProductPermission.EditCustomFields) &&
!isVersionsView
}
isVersionsView={isVersionsView}
permissions={dataProductPermission}
type={DocumentationEntity.DATA_PRODUCT}
viewAllPermission={dataProductPermission.ViewAll}
onExtensionUpdate={handelExtensionUpdate}
onUpdate={(data: Domain | DataProduct) =>
onUpdate(data as DataProduct)

View File

@ -122,7 +122,7 @@ const DomainDetailsPage = ({
}: DomainDetailsPageProps) => {
const { t } = useTranslation();
const [form] = useForm();
const { getEntityPermission } = usePermissionProvider();
const { getEntityPermission, permissions } = usePermissionProvider();
const history = useHistory();
const { tab: activeTab, version } =
useParams<{ tab: string; version: string }>();
@ -202,21 +202,29 @@ const DomainDetailsPage = ({
}, [domainPermission]);
const addButtonContent = [
{
label: t('label.asset-plural'),
key: '1',
onClick: () => setAssetModalVisible(true),
},
{
label: t('label.sub-domain-plural'),
key: '2',
onClick: () => setShowAddSubDomainModal(true),
},
{
label: t('label.data-product-plural'),
key: '3',
onClick: () => setShowAddDataProductModal(true),
},
...(domainPermission.Create
? [
{
label: t('label.asset-plural'),
key: '1',
onClick: () => setAssetModalVisible(true),
},
{
label: t('label.sub-domain-plural'),
key: '2',
onClick: () => setShowAddSubDomainModal(true),
},
]
: []),
...(permissions.dataProduct.Create
? [
{
label: t('label.data-product-plural'),
key: '3',
onClick: () => setShowAddDataProductModal(true),
},
]
: []),
];
const fetchSubDomains = useCallback(async () => {
@ -511,6 +519,7 @@ const DomainDetailsPage = ({
<DocumentationTab
domain={domain}
isVersionsView={isVersionsView}
permissions={domainPermission}
onUpdate={(data: Domain | DataProduct) => onUpdate(data as Domain)}
/>
),
@ -667,7 +676,7 @@ const DomainDetailsPage = ({
</Col>
<Col className="p-x-md" flex="320px">
<div style={{ textAlign: 'right' }}>
{!isVersionsView && domainPermission.Create && (
{!isVersionsView && addButtonContent.length > 0 && (
<Dropdown
className="m-l-xs"
data-testid="domain-details-add-button-menu"

View File

@ -23,7 +23,6 @@ import DomainTypeSelectForm from '../../../../components/Domain/DomainTypeSelect
import { DE_ACTIVE_COLOR } from '../../../../constants/constants';
import { EntityField } from '../../../../constants/Feeds.constants';
import { COMMON_RESIZABLE_PANEL_CONFIG } from '../../../../constants/ResizablePanel.constants';
import { usePermissionProvider } from '../../../../context/PermissionProvider/PermissionProvider';
import { ResourceEntity } from '../../../../context/PermissionProvider/PermissionProvider.interface';
import { EntityType, TabSpecificField } from '../../../../enums/entity.enum';
import { DataProduct } from '../../../../generated/entity/domains/dataProduct';
@ -31,7 +30,6 @@ import {
Domain,
DomainType,
} from '../../../../generated/entity/domains/domain';
import { Operation } from '../../../../generated/entity/policies/policy';
import {
ChangeDescription,
EntityReference,
@ -42,7 +40,6 @@ import {
getEntityVersionByField,
getOwnerVersionLabel,
} from '../../../../utils/EntityVersionUtils';
import { checkPermission } from '../../../../utils/PermissionsUtils';
import { CustomPropertyTable } from '../../../common/CustomPropertyTable/CustomPropertyTable';
import FormItemLabel from '../../../common/Form/FormItemLabel';
import ResizablePanels from '../../../common/ResizablePanels/ResizablePanels';
@ -57,13 +54,11 @@ const DocumentationTab = ({
domain,
onUpdate,
onExtensionUpdate,
editCustomAttributePermission,
viewAllPermission,
isVersionsView = false,
type = DocumentationEntity.DOMAIN,
permissions,
}: DocumentationTabProps) => {
const { t } = useTranslation();
const { permissions } = usePermissionProvider();
const [isDescriptionEditable, setIsDescriptionEditable] =
useState<boolean>(false);
const [editDomainType, setEditDomainType] = useState(false);
@ -72,40 +67,40 @@ const DocumentationTab = ({
? ResourceEntity.DOMAIN
: ResourceEntity.DATA_PRODUCT;
const { editDescriptionPermission, editOwnerPermission, editAllPermission } =
useMemo(() => {
if (isVersionsView) {
return {
editDescriptionPermission: false,
editOwnerPermission: false,
editAllPermission: false,
};
}
const editDescription = checkPermission(
Operation.EditDescription,
resourceType,
permissions
);
const editOwner = checkPermission(
Operation.EditOwners,
resourceType,
permissions
);
const editAll = checkPermission(
Operation.EditAll,
resourceType,
permissions
);
const {
editDescriptionPermission,
editOwnerPermission,
editAllPermission,
editCustomAttributePermission,
viewAllPermission,
} = useMemo(() => {
if (isVersionsView) {
return {
editDescriptionPermission: editDescription || editAll,
editOwnerPermission: editOwner || editAll,
editAllPermission: editAll,
editDescriptionPermission: false,
editOwnerPermission: false,
editAllPermission: false,
editCustomAttributePermission: false,
};
}, [permissions, isVersionsView, resourceType]);
}
const editDescription = permissions?.EditDescription;
const editOwner = permissions?.EditOwners;
const editAll = permissions?.EditAll;
const editCustomAttribute = permissions?.EditCustomFields;
const viewAll = permissions?.ViewAll;
return {
editDescriptionPermission: editAll || editDescription,
editOwnerPermission: editAll || editOwner,
editAllPermission: editAll,
editCustomAttributePermission: editAll || editCustomAttribute,
viewAllPermission: viewAll,
};
}, [permissions, isVersionsView, resourceType]);
const description = useMemo(
() =>

View File

@ -10,6 +10,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { OperationPermission } from '../../../../context/PermissionProvider/PermissionProvider.interface';
import { DataProduct } from '../../../../generated/entity/domains/dataProduct';
import { Domain } from '../../../../generated/entity/domains/domain';
@ -19,8 +20,7 @@ export interface DocumentationTabProps {
isVersionsView?: boolean;
type?: DocumentationEntity;
onExtensionUpdate?: (updatedDataProduct: DataProduct) => Promise<void>;
editCustomAttributePermission?: boolean;
viewAllPermission?: boolean;
permissions?: OperationPermission;
}
export enum DocumentationEntity {

View File

@ -14,6 +14,7 @@ import { render, screen } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { MOCK_DOMAIN } from '../../../../mocks/Domains.mock';
import { MOCK_PERMISSIONS } from '../../../../mocks/Glossary.mock';
import DocumentationTab from './DocumentationTab.component';
// Mock the onUpdate function
@ -23,6 +24,7 @@ const defaultProps = {
domain: MOCK_DOMAIN,
onUpdate: mockOnUpdate,
isVersionsView: false,
permissions: MOCK_PERMISSIONS,
};
jest.mock('../../../common/EntityDescription/DescriptionV1', () => {
@ -33,10 +35,6 @@ jest.mock('../../../common/ProfilePicture/ProfilePicture', () =>
jest.fn().mockReturnValue(<>ProfilePicture</>)
);
jest.mock('../../../../utils/PermissionsUtils', () => ({
checkPermission: jest.fn().mockReturnValue(true),
}));
describe('DocumentationTab', () => {
it('should render the initial content', () => {
const { getByTestId } = render(<DocumentationTab {...defaultProps} />, {