diff --git a/openmetadata-ui/src/main/resources/ui/playwright.config.ts b/openmetadata-ui/src/main/resources/ui/playwright.config.ts index 4108f316d4a..c47a3699213 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright.config.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright.config.ts @@ -63,6 +63,11 @@ export default defineConfig({ { name: 'setup', testMatch: '**/*.setup.ts', + teardown: 'restore-policies', + }, + { + name: 'restore-policies', + testMatch: '**/auth.teardown.ts', }, { name: 'chromium', diff --git a/openmetadata-ui/src/main/resources/ui/playwright/constant/conditionalPermissions.ts b/openmetadata-ui/src/main/resources/ui/playwright/constant/conditionalPermissions.ts new file mode 100644 index 00000000000..e760f988479 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/constant/conditionalPermissions.ts @@ -0,0 +1,175 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PolicyClass } from '../support/access-control/PoliciesClass'; +import { RolesClass } from '../support/access-control/RolesClass'; +import { ApiCollectionClass } from '../support/entity/ApiCollectionClass'; +import { ContainerClass } from '../support/entity/ContainerClass'; +import { DashboardClass } from '../support/entity/DashboardClass'; +import { MlModelClass } from '../support/entity/MlModelClass'; +import { PipelineClass } from '../support/entity/PipelineClass'; +import { SearchIndexClass } from '../support/entity/SearchIndexClass'; +import { TableClass } from '../support/entity/TableClass'; +import { TopicClass } from '../support/entity/TopicClass'; +import { EntityData } from '../support/interfaces/ConditionalPermissions.interface'; +import { UserClass } from '../support/user/UserClass'; +import { ServiceTypes } from './settings'; + +export const isOwnerPolicy = new PolicyClass(); +export const matchAnyTagPolicy = new PolicyClass(); +export const isOwnerRole = new RolesClass(); +export const matchAnyTagRole = new RolesClass(); +export const userWithOwnerPermission = new UserClass(); +export const userWithTagPermission = new UserClass(); +export const apiCollectionWithOwner = new ApiCollectionClass(); +export const apiCollectionWithTag = new ApiCollectionClass(); +export const containerWithOwner = new ContainerClass(); +export const containerWithTag = new ContainerClass(); +export const dashboardWithOwner = new DashboardClass(); +export const dashboardWithTag = new DashboardClass(); +export const mlModelWithOwner = new MlModelClass(); +export const mlModelWithTag = new MlModelClass(); +export const pipelineWithOwner = new PipelineClass(); +export const pipelineWithTag = new PipelineClass(); +export const searchIndexWithOwner = new SearchIndexClass(); +export const searchIndexWithTag = new SearchIndexClass(); +export const tableWithOwner = new TableClass(); +export const tableWithTag = new TableClass(); +export const topicWithOwner = new TopicClass(); +export const topicWithTag = new TopicClass(); + +const withOwner = { + apiCollectionWithOwner, + containerWithOwner, + dashboardWithOwner, + mlModelWithOwner, + pipelineWithOwner, + searchIndexWithOwner, + tableWithOwner, + topicWithOwner, +}; + +const withTag = { + apiCollectionWithTag, + containerWithTag, + dashboardWithTag, + mlModelWithTag, + pipelineWithTag, + searchIndexWithTag, + tableWithTag, + topicWithTag, +}; + +export const assetsData = [ + { + asset: ServiceTypes.API_SERVICES, + withOwner: apiCollectionWithOwner, + withTag: apiCollectionWithTag, + childTabId: 'collections', + assetOwnerUrl: `/service/${apiCollectionWithOwner.serviceType}`, + assetTagUrl: `/service/${apiCollectionWithTag.serviceType}`, + }, + { + asset: ServiceTypes.STORAGE_SERVICES, + withOwner: containerWithOwner, + withTag: containerWithTag, + childTabId: 'containers', + assetOwnerUrl: `/service/${containerWithOwner.serviceType}`, + assetTagUrl: `/service/${containerWithTag.serviceType}`, + }, + { + asset: ServiceTypes.DASHBOARD_SERVICES, + withOwner: dashboardWithOwner, + withTag: dashboardWithTag, + childTabId: 'dashboards', + childTabId2: 'data-model', + childTableId2: 'data-models-table', + assetOwnerUrl: `/service/${dashboardWithOwner.serviceType}`, + assetTagUrl: `/service/${dashboardWithTag.serviceType}`, + }, + { + asset: ServiceTypes.ML_MODEL_SERVICES, + withOwner: mlModelWithOwner, + withTag: mlModelWithTag, + childTabId: 'ml models', + assetOwnerUrl: `/service/${mlModelWithOwner.serviceType}`, + assetTagUrl: `/service/${mlModelWithTag.serviceType}`, + }, + { + asset: ServiceTypes.PIPELINE_SERVICES, + withOwner: pipelineWithOwner, + withTag: pipelineWithTag, + childTabId: 'pipelines', + assetOwnerUrl: `/service/${pipelineWithOwner.serviceType}`, + assetTagUrl: `/service/${pipelineWithTag.serviceType}`, + }, + // TODO: Uncomment when search index permission issue is fixed + // { + // asset: ServiceTypes.SEARCH_SERVICES, + // withOwner: searchIndexWithOwner, + // withTag: searchIndexWithTag, + // childTabId: 'search indexes', + // assetOwnerUrl: `/service/${searchIndexWithOwner.serviceType}`, + // assetTagUrl: `/service/${searchIndexWithTag.serviceType}`, + // }, + { + asset: ServiceTypes.DATABASE_SERVICES, + withOwner: tableWithOwner, + withTag: tableWithTag, + childTabId: 'databases', + assetOwnerUrl: `/service/${tableWithOwner.serviceType}`, + assetTagUrl: `/service/${tableWithTag.serviceType}`, + }, + { + asset: ServiceTypes.MESSAGING_SERVICES, + withOwner: topicWithOwner, + withTag: topicWithTag, + childTabId: 'topics', + assetOwnerUrl: `/service/${topicWithOwner.serviceType}`, + assetTagUrl: `/service/${topicWithTag.serviceType}`, + }, + { + asset: 'database', + withOwner: tableWithOwner, + withTag: tableWithTag, + childTabId: 'schema', + assetOwnerUrl: `/database`, + assetTagUrl: `/database`, + }, + { + asset: 'databaseSchema', + withOwner: tableWithOwner, + withTag: tableWithTag, + childTabId: 'table', + assetOwnerUrl: `/databaseSchema`, + assetTagUrl: `/databaseSchema`, + }, + { + asset: 'container', + withOwner: containerWithOwner, + withTag: containerWithTag, + childTabId: 'children', + assetOwnerUrl: `/container`, + assetTagUrl: `/container`, + }, +]; + +export const conditionalPermissionsEntityData: EntityData = { + isOwnerPolicy, + matchAnyTagPolicy, + isOwnerRole, + matchAnyTagRole, + userWithOwnerPermission, + userWithTagPermission, + withOwner, + withTag, +}; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/constant/permission.ts b/openmetadata-ui/src/main/resources/ui/playwright/constant/permission.ts index 7c810b833c7..66e87f4079a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/constant/permission.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/constant/permission.ts @@ -77,6 +77,36 @@ export const DATA_CONSUMER_RULES: PolicyRulesType[] = [ }, ]; +export const VIEW_ALL_RULE: PolicyRulesType[] = [ + { + name: 'OrganizationPolicy-ViewAll-Rule', + description: 'Allow all users to view all metadata', + resources: ['All'], + operations: ['ViewAll'], + effect: 'allow', + }, +]; + +export const VIEW_ALL_WITH_IS_OWNER: PolicyRulesType[] = [ + { + name: 'viewAll-IsOwner', + resources: ['All'], + operations: ['ViewAll'], + effect: 'allow', + condition: 'isOwner()', + }, +]; + +export const VIEW_ALL_WITH_MATCH_TAG_CONDITION: PolicyRulesType[] = [ + { + name: 'viewAll-MatchTag', + resources: ['All'], + operations: ['ViewAll'], + effect: 'allow', + condition: "matchAnyTag('PersonalData.Personal')", + }, +]; + export const EDIT_USER_FOR_TEAM_RULES: PolicyRulesType[] = [ { name: 'EditUserTeams-EditRule', @@ -104,13 +134,6 @@ export const ORGANIZATION_POLICY_RULES: PolicyRulesType[] = [ resources: ['All'], condition: 'isOwner()', }, - { - name: 'OrganizationPolicy-ViewAll-Rule', - description: 'Allow all users to discover data assets.', - effect: 'allow', - operations: ['ViewAll'], - resources: ['All'], - }, ]; export const GLOBAL_SETTING_PERMISSIONS: Record< diff --git a/openmetadata-ui/src/main/resources/ui/playwright/constant/service.ts b/openmetadata-ui/src/main/resources/ui/playwright/constant/service.ts index 3d5bc91321c..6f40d87c1e6 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/constant/service.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/constant/service.ts @@ -11,7 +11,7 @@ * limitations under the License. */ import { uuid } from '../utils/common'; -import { GlobalSettingOptions } from './settings'; +import { GlobalSettingOptions, ServiceTypes } from './settings'; export const SERVICE_TYPE = { Database: GlobalSettingOptions.DATABASES, @@ -26,49 +26,38 @@ export const SERVICE_TYPE = { ApiService: GlobalSettingOptions.APIS, }; -export const SERVICE_CATEGORIES = { - DATABASE_SERVICES: 'databaseServices', - MESSAGING_SERVICES: 'messagingServices', - PIPELINE_SERVICES: 'pipelineServices', - DASHBOARD_SERVICES: 'dashboardServices', - ML_MODEL_SERVICES: 'mlmodelServices', - STORAGE_SERVICES: 'storageServices', - METADATA_SERVICES: 'metadataServices', - SEARCH_SERVICES: 'searchServices', -}; - export const VISIT_SERVICE_PAGE_DETAILS = { [SERVICE_TYPE.Database]: { settingsMenuId: GlobalSettingOptions.DATABASES, - serviceCategory: SERVICE_CATEGORIES.DATABASE_SERVICES, + serviceCategory: ServiceTypes.DATABASE_SERVICES, }, [SERVICE_TYPE.Messaging]: { settingsMenuId: GlobalSettingOptions.MESSAGING, - serviceCategory: SERVICE_CATEGORIES.MESSAGING_SERVICES, + serviceCategory: ServiceTypes.MESSAGING_SERVICES, }, [SERVICE_TYPE.Dashboard]: { settingsMenuId: GlobalSettingOptions.DASHBOARDS, - serviceCategory: SERVICE_CATEGORIES.DASHBOARD_SERVICES, + serviceCategory: ServiceTypes.DASHBOARD_SERVICES, }, [SERVICE_TYPE.Pipeline]: { settingsMenuId: GlobalSettingOptions.PIPELINES, - serviceCategory: SERVICE_CATEGORIES.PIPELINE_SERVICES, + serviceCategory: ServiceTypes.PIPELINE_SERVICES, }, [SERVICE_TYPE.MLModels]: { settingsMenuId: GlobalSettingOptions.MLMODELS, - serviceCategory: SERVICE_CATEGORIES.ML_MODEL_SERVICES, + serviceCategory: ServiceTypes.ML_MODEL_SERVICES, }, [SERVICE_TYPE.Storage]: { settingsMenuId: GlobalSettingOptions.STORAGES, - serviceCategory: SERVICE_CATEGORIES.STORAGE_SERVICES, + serviceCategory: ServiceTypes.STORAGE_SERVICES, }, [SERVICE_TYPE.Search]: { settingsMenuId: GlobalSettingOptions.SEARCH, - serviceCategory: SERVICE_CATEGORIES.SEARCH_SERVICES, + serviceCategory: ServiceTypes.SEARCH_SERVICES, }, [SERVICE_TYPE.Metadata]: { settingsMenuId: GlobalSettingOptions.METADATA, - serviceCategory: SERVICE_CATEGORIES.METADATA_SERVICES, + serviceCategory: ServiceTypes.METADATA_SERVICES, }, }; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/constant/settings.ts b/openmetadata-ui/src/main/resources/ui/playwright/constant/settings.ts index 703f32e0535..5cc52c15a12 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/constant/settings.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/constant/settings.ts @@ -21,6 +21,18 @@ export enum GlobalSettingsMenuCategory { APPLICATIONS = 'apps', } +export enum ServiceTypes { + API_SERVICES = 'apiServices', + DATABASE_SERVICES = 'databaseServices', + MESSAGING_SERVICES = 'messagingServices', + PIPELINE_SERVICES = 'pipelineServices', + DASHBOARD_SERVICES = 'dashboardServices', + ML_MODEL_SERVICES = 'mlmodelServices', + STORAGE_SERVICES = 'storageServices', + METADATA_SERVICES = 'metadataServices', + SEARCH_SERVICES = 'searchServices', +} + export enum GlobalSettingOptions { USERS = 'users', ADMINS = 'admins', @@ -80,6 +92,10 @@ export enum GlobalSettingOptions { export const SETTINGS_OPTIONS_PATH = { // Services + [GlobalSettingOptions.API_COLLECTIONS]: [ + GlobalSettingsMenuCategory.SERVICES, + `${GlobalSettingsMenuCategory.SERVICES}.${GlobalSettingOptions.API_COLLECTIONS}`, + ], [GlobalSettingOptions.DATABASES]: [ GlobalSettingsMenuCategory.SERVICES, `${GlobalSettingsMenuCategory.SERVICES}.${GlobalSettingOptions.DATABASES}`, diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ConditionalPermissions.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ConditionalPermissions.spec.ts new file mode 100644 index 00000000000..15965848cfe --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Flow/ConditionalPermissions.spec.ts @@ -0,0 +1,113 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Page, test as base } from '@playwright/test'; +import { startCase } from 'lodash'; +import { + assetsData, + userWithOwnerPermission, + userWithTagPermission, +} from '../../constant/conditionalPermissions'; +import { performAdminLogin } from '../../utils/admin'; +import { + checkViewAllPermission, + conditionalPermissionsCleanup, + conditionalPermissionsPrerequisites, + getEntityFQN, +} from '../../utils/conditionalPermissions'; + +const test = base.extend<{ + user1Page: Page; + user2Page: Page; +}>({ + user1Page: async ({ browser }, use) => { + const page = await browser.newPage(); + await userWithOwnerPermission.login(page); + await use(page); + await page.close(); + }, + user2Page: async ({ browser }, use) => { + const page = await browser.newPage(); + await userWithTagPermission.login(page); + await use(page); + await page.close(); + }, +}); + +test.beforeAll(async ({ browser }) => { + test.slow(); + + const { apiContext, afterAction } = await performAdminLogin(browser); + await conditionalPermissionsPrerequisites(apiContext); + await afterAction(); +}); + +test.afterAll(async ({ browser }) => { + test.slow(); + + const { apiContext, afterAction } = await performAdminLogin(browser); + await conditionalPermissionsCleanup(apiContext); + await afterAction(); +}); + +for (const serviceData of assetsData) { + const { + asset, + withOwner, + withTag, + assetOwnerUrl, + assetTagUrl, + childTabId, + childTabId2, + childTableId2, + } = serviceData; + + test(`User with owner permission can only view owned ${startCase( + asset + )}`, async ({ user1Page: page }) => { + // Get the FQNs of both assets + const ownerAssetName = getEntityFQN(asset, withOwner); + const tagAssetName = getEntityFQN(asset, withTag); + + const ownerAssetURL = `${assetOwnerUrl}/${ownerAssetName}`; + const tagAssetURL = `${assetTagUrl}/${tagAssetName}`; + + await checkViewAllPermission({ + page, + url1: ownerAssetURL, + url2: tagAssetURL, + childTabId, + childTabId2, + childTableId2, + }); + }); + + test(`User with matchAnyTag permission can only view ${startCase( + asset + )} with the tag`, async ({ user2Page: page }) => { + // Get the FQNs of both assets + const ownerAssetName = getEntityFQN(asset, withOwner); + const tagAssetName = getEntityFQN(asset, withTag); + + const ownerAssetURL = `${assetOwnerUrl}/${ownerAssetName}`; + const tagAssetURL = `${assetTagUrl}/${tagAssetName}`; + + await checkViewAllPermission({ + page, + url1: tagAssetURL, + url2: ownerAssetURL, + childTabId, + childTabId2, + childTableId2, + }); + }); +} diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/auth.teardown.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/auth.teardown.ts new file mode 100644 index 00000000000..bba61a4ba67 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/auth.teardown.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { test as teardown } from '@playwright/test'; +import { AdminClass } from '../support/user/AdminClass'; +import { resetPolicyChanges } from '../utils/authTeardown'; + +teardown('restore the organization roles and policies', async ({ page }) => { + const admin = new AdminClass(); + + // Reset the default organization roles and policies + await resetPolicyChanges(page, admin); +}); diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ApiCollectionClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ApiCollectionClass.ts index f2a281a808c..27edd96029f 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ApiCollectionClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ApiCollectionClass.ts @@ -13,6 +13,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; import { visitEntityPage } from '../../utils/entity'; import { visitServiceDetailsPage } from '../../utils/service'; @@ -149,6 +150,8 @@ export class ApiCollectionClass extends EntityClass { constructor(name?: string) { super(EntityTypeEndpoint.API_COLLECTION); + this.serviceCategory = SERVICE_TYPE.ApiService; + this.serviceType = ServiceTypes.API_SERVICES; this.service.name = name ?? this.service.name; this.type = 'Api Collection'; } diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ApiEndpointClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ApiEndpointClass.ts index 0eaa969a774..9a0b9efa91c 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ApiEndpointClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ApiEndpointClass.ts @@ -13,6 +13,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; import { visitEntityPage } from '../../utils/entity'; import { @@ -153,6 +154,7 @@ export class ApiEndpointClass extends EntityClass { super(EntityTypeEndpoint.API_ENDPOINT); this.service.name = name ?? this.service.name; this.serviceCategory = SERVICE_TYPE.ApiService; + this.serviceType = ServiceTypes.API_SERVICES; this.type = 'ApiEndpoint'; this.childrenTabId = 'schema'; this.childrenSelectorId = this.children[0].name; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts index e3559452b0b..1ad4c8d4d57 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/ContainerClass.ts @@ -13,6 +13,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; import { visitEntityPage } from '../../utils/entity'; import { @@ -24,6 +25,7 @@ import { EntityClass } from './EntityClass'; export class ContainerClass extends EntityClass { private containerName = `pw-container-${uuid()}`; + private childContainerName = `pw-container-${uuid()}`; service = { name: `pw-storage-service-${uuid()}`, serviceType: 'S3', @@ -45,14 +47,21 @@ export class ContainerClass extends EntityClass { displayName: this.containerName, service: this.service.name, }; + childContainer = { + name: this.childContainerName, + displayName: this.childContainerName, + service: this.service.name, + }; serviceResponseData: ResponseDataType = {} as ResponseDataType; entityResponseData: ResponseDataWithServiceType = {} as ResponseDataWithServiceType; + childResponseData: ResponseDataType = {} as ResponseDataType; constructor(name?: string) { super(EntityTypeEndpoint.Container); this.service.name = name ?? this.service.name; + this.serviceType = ServiceTypes.STORAGE_SERVICES; this.type = 'Container'; this.serviceCategory = SERVICE_TYPE.Storage; } @@ -71,6 +80,20 @@ export class ContainerClass extends EntityClass { this.serviceResponseData = await serviceResponse.json(); this.entityResponseData = await entityResponse.json(); + const childContainer = { + ...this.childContainer, + parent: { + id: this.entityResponseData.id, + type: 'container', + }, + }; + + const childResponse = await apiContext.post('/api/v1/containers', { + data: childContainer, + }); + + this.childResponseData = await childResponse.json(); + return { service: serviceResponse.body, entity: entityResponse.body, diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardClass.ts index bea3c4579a3..14777bda65a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardClass.ts @@ -13,6 +13,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; import { visitEntityPage } from '../../utils/entity'; import { @@ -24,6 +25,7 @@ import { EntityClass } from './EntityClass'; export class DashboardClass extends EntityClass { private dashboardName = `pw-dashboard-${uuid()}`; + private dashboardDataModelName = `pw-dashboard-data-model-${uuid()}`; service = { name: `pw-dashboard-service-${uuid()}`, serviceType: 'Superset', @@ -50,10 +52,28 @@ export class DashboardClass extends EntityClass { displayName: this.dashboardName, service: this.service.name, }; + children = [ + { + name: 'country_name', + dataType: 'VARCHAR', + dataLength: 256, + dataTypeDisplay: 'varchar', + description: 'Name of the country.', + }, + ]; + dataModel = { + name: this.dashboardDataModelName, + displayName: this.dashboardDataModelName, + service: this.service.name, + columns: this.children, + dataModelType: 'SupersetDataModel', + }; serviceResponseData: ResponseDataType = {} as ResponseDataType; entityResponseData: ResponseDataWithServiceType = {} as ResponseDataWithServiceType; + dataModelResponseData: ResponseDataWithServiceType = + {} as ResponseDataWithServiceType; chartsResponseData: ResponseDataType = {} as ResponseDataType; constructor(name?: string) { @@ -61,6 +81,7 @@ export class DashboardClass extends EntityClass { this.service.name = name ?? this.service.name; this.type = 'Dashboard'; this.serviceCategory = SERVICE_TYPE.Dashboard; + this.serviceType = ServiceTypes.DASHBOARD_SERVICES; } async create(apiContext: APIRequestContext) { @@ -80,9 +101,16 @@ export class DashboardClass extends EntityClass { charts: [`${this.service.name}.${this.charts.name}`], }, }); + const dataModelResponse = await apiContext.post( + '/api/v1/dashboard/datamodels', + { + data: this.dataModel, + } + ); this.serviceResponseData = await serviceResponse.json(); this.chartsResponseData = await chartsResponse.json(); + this.dataModelResponseData = await dataModelResponse.json(); this.entityResponseData = await entityResponse.json(); return { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardDataModelClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardDataModelClass.ts index 24d824720cb..738460fdb74 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardDataModelClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DashboardDataModelClass.ts @@ -13,6 +13,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; import { visitEntityPage } from '../../utils/entity'; import { @@ -70,6 +71,7 @@ export class DashboardDataModelClass extends EntityClass { this.childrenTabId = 'model'; this.childrenSelectorId = this.children[0].name; this.serviceCategory = SERVICE_TYPE.Dashboard; + this.serviceType = ServiceTypes.DASHBOARD_SERVICES; } async create(apiContext: APIRequestContext) { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DatabaseClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DatabaseClass.ts index b5d2a634ad7..df1bdfc0cbd 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DatabaseClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DatabaseClass.ts @@ -13,6 +13,7 @@ import { APIRequestContext, expect, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { assignDomain, removeDomain, @@ -127,6 +128,7 @@ export class DatabaseClass extends EntityClass { super(EntityTypeEndpoint.Database); this.service.name = name ?? this.service.name; this.type = 'Database'; + this.serviceType = ServiceTypes.DATABASE_SERVICES; } async create(apiContext: APIRequestContext) { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DatabaseSchemaClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DatabaseSchemaClass.ts index 45909c1e466..38aa8de0b95 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DatabaseSchemaClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/DatabaseSchemaClass.ts @@ -13,6 +13,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; import { visitServiceDetailsPage } from '../../utils/service'; import { @@ -61,6 +62,7 @@ export class DatabaseSchemaClass extends EntityClass { super(EntityTypeEndpoint.DatabaseSchema); this.service.name = name ?? this.service.name; this.type = 'Database Schema'; + this.serviceType = ServiceTypes.DATABASE_SERVICES; } async create(apiContext: APIRequestContext) { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts index 6df7ae49e18..d3b91e20cf7 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/EntityClass.ts @@ -12,7 +12,7 @@ */ import { APIRequestContext, Page } from '@playwright/test'; import { CustomPropertySupportedEntityList } from '../../constant/customProperty'; -import { GlobalSettingOptions } from '../../constant/settings'; +import { GlobalSettingOptions, ServiceTypes } from '../../constant/settings'; import { assignDomain, removeDomain, updateDomain } from '../../utils/common'; import { createCustomPropertyForEntity, @@ -56,6 +56,7 @@ import { EntityTypeEndpoint, ENTITY_PATH } from './Entity.interface'; export class EntityClass { type = ''; serviceCategory?: GlobalSettingOptions; + serviceType?: ServiceTypes; childrenTabId?: string; childrenSelectorId?: string; endpoint: EntityTypeEndpoint; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/MlModelClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/MlModelClass.ts index fe415769f51..b959705e307 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/MlModelClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/MlModelClass.ts @@ -13,6 +13,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; import { visitEntityPage } from '../../utils/entity'; import { @@ -64,6 +65,7 @@ export class MlModelClass extends EntityClass { this.childrenTabId = 'features'; this.childrenSelectorId = `feature-card-${this.children[0].name}`; this.serviceCategory = SERVICE_TYPE.MLModels; + this.serviceType = ServiceTypes.ML_MODEL_SERVICES; } async create(apiContext: APIRequestContext) { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/PipelineClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/PipelineClass.ts index 30a7c6adc10..6f82dd49626 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/PipelineClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/PipelineClass.ts @@ -13,6 +13,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; import { visitEntityPage } from '../../utils/entity'; import { @@ -59,6 +60,7 @@ export class PipelineClass extends EntityClass { this.childrenTabId = 'tasks'; this.childrenSelectorId = this.children[0].name; this.serviceCategory = SERVICE_TYPE.Pipeline; + this.serviceType = ServiceTypes.PIPELINE_SERVICES; } async create(apiContext: APIRequestContext) { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/SearchIndexClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/SearchIndexClass.ts index 9f75df65cb1..46bd8b9c61d 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/SearchIndexClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/SearchIndexClass.ts @@ -13,6 +13,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; import { visitEntityPage } from '../../utils/entity'; import { @@ -105,6 +106,7 @@ export class SearchIndexClass extends EntityClass { this.childrenTabId = 'fields'; this.childrenSelectorId = this.children[0].fullyQualifiedName; this.serviceCategory = SERVICE_TYPE.Search; + this.serviceType = ServiceTypes.SEARCH_SERVICES; } async create(apiContext: APIRequestContext) { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/StoredProcedureClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/StoredProcedureClass.ts index b0b41f3d4e0..1c7c23ebeaf 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/StoredProcedureClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/StoredProcedureClass.ts @@ -13,6 +13,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; import { visitEntityPage } from '../../utils/entity'; import { @@ -71,6 +72,7 @@ export class StoredProcedureClass extends EntityClass { this.service.name = name ?? this.service.name; this.serviceCategory = SERVICE_TYPE.Database; this.type = 'Store Procedure'; + this.serviceType = ServiceTypes.DATABASE_SERVICES; } async create(apiContext: APIRequestContext) { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts index 51d07d7ec0e..130981f888a 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TableClass.ts @@ -14,6 +14,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { isEmpty } from 'lodash'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; import { visitEntityPage } from '../../utils/entity'; import { @@ -125,6 +126,7 @@ export class TableClass extends EntityClass { super(EntityTypeEndpoint.Table); this.service.name = name ?? this.service.name; this.serviceCategory = SERVICE_TYPE.Database; + this.serviceType = ServiceTypes.DATABASE_SERVICES; this.type = 'Table'; this.childrenTabId = 'schema'; this.childrenSelectorId = `${this.entity.databaseSchema}.${this.entity.name}.${this.children[0].name}`; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TopicClass.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TopicClass.ts index 82ce4e87f5b..b223f22d4fc 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TopicClass.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/entity/TopicClass.ts @@ -13,6 +13,7 @@ import { APIRequestContext, Page } from '@playwright/test'; import { Operation } from 'fast-json-patch'; import { SERVICE_TYPE } from '../../constant/service'; +import { ServiceTypes } from '../../constant/settings'; import { uuid } from '../../utils/common'; import { visitEntityPage } from '../../utils/entity'; import { @@ -107,6 +108,7 @@ export class TopicClass extends EntityClass { this.childrenTabId = 'schema'; this.childrenSelectorId = this.children[0].name; this.serviceCategory = SERVICE_TYPE.Messaging; + this.serviceType = ServiceTypes.MESSAGING_SERVICES; } async create(apiContext: APIRequestContext) { diff --git a/openmetadata-ui/src/main/resources/ui/playwright/support/interfaces/ConditionalPermissions.interface.ts b/openmetadata-ui/src/main/resources/ui/playwright/support/interfaces/ConditionalPermissions.interface.ts new file mode 100644 index 00000000000..15bca0e688d --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/support/interfaces/ConditionalPermissions.interface.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { PolicyClass } from '../access-control/PoliciesClass'; +import { RolesClass } from '../access-control/RolesClass'; +import { ApiCollectionClass } from '../entity/ApiCollectionClass'; +import { ContainerClass } from '../entity/ContainerClass'; +import { DashboardClass } from '../entity/DashboardClass'; +import { MlModelClass } from '../entity/MlModelClass'; +import { PipelineClass } from '../entity/PipelineClass'; +import { SearchIndexClass } from '../entity/SearchIndexClass'; +import { TableClass } from '../entity/TableClass'; +import { TopicClass } from '../entity/TopicClass'; +import { UserClass } from '../user/UserClass'; + +export interface EntityData { + isOwnerPolicy: PolicyClass; + matchAnyTagPolicy: PolicyClass; + isOwnerRole: RolesClass; + matchAnyTagRole: RolesClass; + userWithOwnerPermission: UserClass; + userWithTagPermission: UserClass; + withOwner: { + apiCollectionWithOwner: ApiCollectionClass; + containerWithOwner: ContainerClass; + dashboardWithOwner: DashboardClass; + mlModelWithOwner: MlModelClass; + pipelineWithOwner: PipelineClass; + searchIndexWithOwner: SearchIndexClass; + tableWithOwner: TableClass; + topicWithOwner: TopicClass; + }; + withTag: { + apiCollectionWithTag: ApiCollectionClass; + containerWithTag: ContainerClass; + dashboardWithTag: DashboardClass; + mlModelWithTag: MlModelClass; + pipelineWithTag: PipelineClass; + searchIndexWithTag: SearchIndexClass; + tableWithTag: TableClass; + topicWithTag: TopicClass; + }; +} + +export type AssetTypes = + | ApiCollectionClass + | ContainerClass + | DashboardClass + | MlModelClass + | PipelineClass + | TableClass + | TopicClass; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/authTeardown.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/authTeardown.ts new file mode 100644 index 00000000000..633a9f0bae1 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/authTeardown.ts @@ -0,0 +1,88 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { APIRequestContext, Page } from '@playwright/test'; +import { + ORGANIZATION_POLICY_RULES, + VIEW_ALL_RULE, +} from '../constant/permission'; +import { AdminClass } from '../support/user/AdminClass'; +import { getApiContext } from './common'; + +export const restoreOrganizationDefaultRole = async ( + apiContext: APIRequestContext +) => { + const organizationTeamResponse = await apiContext + .get(`/api/v1/teams/name/Organization`) + .then((res) => res.json()); + + const dataConsumerRoleResponse = await apiContext + .get('/api/v1/roles/name/DataConsumer') + .then((res) => res.json()); + + await apiContext.patch(`/api/v1/teams/${organizationTeamResponse.id}`, { + data: [ + { + op: 'replace', + path: '/defaultRoles', + value: [ + { + id: dataConsumerRoleResponse.id, + type: 'role', + }, + ], + }, + ], + headers: { + 'Content-Type': 'application/json-patch+json', + }, + }); +}; + +export const updateDefaultOrganizationPolicy = async ( + apiContext: APIRequestContext +) => { + const orgPolicyResponse = await apiContext + .get('/api/v1/policies/name/OrganizationPolicy') + .then((response) => response.json()); + + await apiContext.patch(`/api/v1/policies/${orgPolicyResponse.id}`, { + data: [ + { + op: 'replace', + path: '/rules', + value: [...ORGANIZATION_POLICY_RULES, ...VIEW_ALL_RULE], + }, + ], + headers: { + 'Content-Type': 'application/json-patch+json', + }, + }); +}; + +const restoreRolesAndPolicies = async (page: Page) => { + const { apiContext, afterAction } = await getApiContext(page); + // Remove organization policy and role + await restoreOrganizationDefaultRole(apiContext); + // update default Organization policy + await updateDefaultOrganizationPolicy(apiContext); + + await afterAction(); +}; + +export const resetPolicyChanges = async (page: Page, admin: AdminClass) => { + await admin.login(page); + await page.waitForURL('**/my-data'); + await restoreRolesAndPolicies(page); + await admin.logout(page); + await page.waitForURL('**/signin'); +}; diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/conditionalPermissions.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/conditionalPermissions.ts new file mode 100644 index 00000000000..82cadb5365f --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/conditionalPermissions.ts @@ -0,0 +1,304 @@ +/* + * Copyright 2025 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { APIRequestContext, expect, Page } from '@playwright/test'; +import { conditionalPermissionsEntityData } from '../constant/conditionalPermissions'; +import { + VIEW_ALL_WITH_IS_OWNER, + VIEW_ALL_WITH_MATCH_TAG_CONDITION, +} from '../constant/permission'; +import { TableClass } from '../support/entity/TableClass'; +import { AssetTypes } from '../support/interfaces/ConditionalPermissions.interface'; +import { redirectToHomePage } from './common'; + +export const conditionalPermissionsPrerequisites = async ( + apiContext: APIRequestContext +) => { + const { + isOwnerPolicy, + matchAnyTagPolicy, + isOwnerRole, + matchAnyTagRole, + userWithOwnerPermission, + userWithTagPermission, + withOwner, + withTag, + } = conditionalPermissionsEntityData; + await userWithOwnerPermission.create(apiContext); + await userWithTagPermission.create(apiContext); + await withOwner.apiCollectionWithOwner.create(apiContext); + await withTag.apiCollectionWithTag.create(apiContext); + await withOwner.containerWithOwner.create(apiContext); + await withTag.containerWithTag.create(apiContext); + await withOwner.dashboardWithOwner.create(apiContext); + await withTag.dashboardWithTag.create(apiContext); + await withOwner.mlModelWithOwner.create(apiContext); + await withTag.mlModelWithTag.create(apiContext); + await withOwner.pipelineWithOwner.create(apiContext); + await withTag.pipelineWithTag.create(apiContext); + await withOwner.searchIndexWithOwner.create(apiContext); + await withTag.searchIndexWithTag.create(apiContext); + await withOwner.tableWithOwner.create(apiContext); + await withTag.tableWithTag.create(apiContext); + await withOwner.topicWithOwner.create(apiContext); + await withTag.topicWithTag.create(apiContext); + + const isOwnerPolicyResponse = await isOwnerPolicy.create( + apiContext, + VIEW_ALL_WITH_IS_OWNER + ); + const matchAnyTagPolicyResponse = await matchAnyTagPolicy.create( + apiContext, + VIEW_ALL_WITH_MATCH_TAG_CONDITION + ); + + const isOwnerRoleResponse = await isOwnerRole.create(apiContext, [ + isOwnerPolicyResponse.fullyQualifiedName, + ]); + const matchAnyTagRoleResponse = await matchAnyTagRole.create(apiContext, [ + matchAnyTagPolicyResponse.fullyQualifiedName, + ]); + + await userWithOwnerPermission.patch({ + apiContext, + patchData: [ + { + op: 'replace', + path: '/roles', + value: [ + { + id: isOwnerRoleResponse.id, + type: 'role', + name: isOwnerRoleResponse.name, + }, + ], + }, + ], + }); + await userWithTagPermission.patch({ + apiContext, + patchData: [ + { + op: 'replace', + path: '/roles', + value: [ + { + id: matchAnyTagRoleResponse.id, + type: 'role', + name: matchAnyTagRoleResponse.name, + }, + ], + }, + ], + }); + + const ownerPatchData = { + data: [ + { + op: 'replace', + path: '/owners', + value: [ + { + id: userWithOwnerPermission.responseData.id, + type: 'user', + }, + ], + }, + ], + headers: { + 'Content-Type': 'application/json-patch+json', + }, + }; + + const tagPatchData = { + data: [ + { + op: 'replace', + path: '/tags', + value: [ + { + tagFQN: 'PersonalData.Personal', + source: 'Classification', + labelType: 'Manual', + name: 'Personal', + state: 'Confirmed', + }, + ], + }, + ], + headers: { + 'Content-Type': 'application/json-patch+json', + }, + }; + + for (const entity of Object.values(withOwner)) { + await apiContext.patch( + `/api/v1/services/${entity.serviceType}/${entity.serviceResponseData.id}`, + ownerPatchData + ); + } + + await apiContext.patch( + `/api/v1/databases/${withOwner.tableWithOwner.databaseResponseData.id}`, + ownerPatchData + ); + + await apiContext.patch( + `/api/v1/databaseSchemas/${withOwner.tableWithOwner.schemaResponseData.id}`, + ownerPatchData + ); + + await apiContext.patch( + `/api/v1/containers/${withOwner.containerWithOwner.entityResponseData.id}`, + ownerPatchData + ); + + for (const entity of Object.values(withTag)) { + await apiContext.patch( + `/api/v1/services/${entity.serviceType}/${entity.serviceResponseData.id}`, + tagPatchData + ); + } + + await apiContext.patch( + `/api/v1/databases/${withTag.tableWithTag.databaseResponseData.id}`, + tagPatchData + ); + + await apiContext.patch( + `/api/v1/databaseSchemas/${withTag.tableWithTag.schemaResponseData.id}`, + tagPatchData + ); + + await apiContext.patch( + `/api/v1/containers/${withTag.containerWithTag.entityResponseData.id}`, + tagPatchData + ); +}; + +export const conditionalPermissionsCleanup = async ( + apiContext: APIRequestContext +) => { + const { + isOwnerPolicy, + matchAnyTagPolicy, + isOwnerRole, + matchAnyTagRole, + userWithOwnerPermission, + userWithTagPermission, + withOwner: { + apiCollectionWithOwner, + containerWithOwner, + dashboardWithOwner, + mlModelWithOwner, + pipelineWithOwner, + searchIndexWithOwner, + tableWithOwner, + topicWithOwner, + }, + withTag: { + apiCollectionWithTag, + containerWithTag, + dashboardWithTag, + mlModelWithTag, + pipelineWithTag, + searchIndexWithTag, + tableWithTag, + topicWithTag, + }, + } = conditionalPermissionsEntityData; + await isOwnerRole.delete(apiContext); + await matchAnyTagRole.delete(apiContext); + await isOwnerPolicy.delete(apiContext); + await matchAnyTagPolicy.delete(apiContext); + await userWithOwnerPermission.delete(apiContext); + await userWithTagPermission.delete(apiContext); + await apiCollectionWithOwner.delete(apiContext); + await apiCollectionWithTag.delete(apiContext); + await containerWithOwner.delete(apiContext); + await containerWithTag.delete(apiContext); + await dashboardWithOwner.delete(apiContext); + await dashboardWithTag.delete(apiContext); + await mlModelWithOwner.delete(apiContext); + await mlModelWithTag.delete(apiContext); + await pipelineWithOwner.delete(apiContext); + await pipelineWithTag.delete(apiContext); + await searchIndexWithOwner.delete(apiContext); + await searchIndexWithTag.delete(apiContext); + await tableWithOwner.delete(apiContext); + await tableWithTag.delete(apiContext); + await topicWithOwner.delete(apiContext); + await topicWithTag.delete(apiContext); +}; + +export const getEntityFQN = (assetName: string, asset: AssetTypes) => { + if (assetName === 'database') { + return (asset as TableClass).databaseResponseData.fullyQualifiedName; + } + if (assetName === 'databaseSchema') { + return (asset as TableClass).schemaResponseData.fullyQualifiedName; + } + if (assetName === 'container') { + return asset.entityResponseData.fullyQualifiedName; + } + + return asset.serviceResponseData.fullyQualifiedName; +}; + +export const checkViewAllPermission = async ({ + page, + url1, + url2, + childTabId, + childTabId2, + childTableId2, +}: { + page: Page; + url1: string; + url2: string; + childTabId: string; + childTabId2?: string; + childTableId2?: string; +}) => { + await redirectToHomePage(page); + + // visit the page of the asset with permission + await page.goto(url1); + + // Check if the details are shown properly + await expect(page.getByTestId('data-assets-header')).toBeAttached(); + + await page.waitForSelector(`[data-testid="${childTabId}"]`); + + await page.click(`[data-testid="${childTabId}"]`); + + await expect( + page + .getByTestId('service-children-table') + .getByTestId('no-data-placeholder') + ).not.toBeAttached(); + + if (childTabId2) { + await page.click(`[data-testid="${childTabId2}"]`); + + await expect( + page.getByTestId(childTableId2 ?? '').getByTestId('no-data-placeholder') + ).not.toBeAttached(); + } + + // visit the page of the asset without permission + await page.goto(url2); + + // Check if the no permissions placeholder is shown + await expect(page.getByTestId('permission-error-placeholder')).toBeAttached(); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseDetailsPage/DatabaseDetailsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseDetailsPage/DatabaseDetailsPage.tsx index 8bee29a444a..d406fdd9712 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseDetailsPage/DatabaseDetailsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseDetailsPage/DatabaseDetailsPage.tsx @@ -355,6 +355,8 @@ const DatabaseDetails: FunctionComponent = () => { if (databasePermission.ViewAll || databasePermission.ViewBasic) { getDetailsByFQN(); fetchDatabaseSchemaCount(); + } else { + setIsDatabaseDetailsLoading(false); } }, [databasePermission, decodedDatabaseFQN]);