mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-28 09:13:58 +00:00
Allow Asset add and remove operation for editTag permission user (#18786)
* Allow Asset add and remove operation for editTag permission user * remove edit all permission check in bulkAssetTag api for tag * fix the permission check by checking specific and the index used to exclude * check editTag permission before updating assets by bulkTagAsset api * fix the button not visible for the non admin user * added playwright for the limited user check aroud asset * restrict add asset button in certification page * minor fix * cleanup aroud tag page and fix usertab count around Team page * use entity type instead of search index --------- Co-authored-by: sonikashah <sonikashah94@gmail.com> Co-authored-by: karanh37 <karanh37@gmail.com>
This commit is contained in:
parent
9a21e77e15
commit
7877d5c14c
@ -220,6 +220,13 @@ public final class CatalogExceptionMessage {
|
|||||||
"Principal: CatalogPrincipal{name='%s'} operations %s not allowed", user, operations);
|
"Principal: CatalogPrincipal{name='%s'} operations %s not allowed", user, operations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String resourcePermissionNotAllowed(
|
||||||
|
String user, List<MetadataOperation> operations, List<String> resources) {
|
||||||
|
return String.format(
|
||||||
|
"Principal: CatalogPrincipal{name='%s'} operations %s not allowed for resources {%s}.",
|
||||||
|
user, operations, resources);
|
||||||
|
}
|
||||||
|
|
||||||
public static String domainPermissionNotAllowed(
|
public static String domainPermissionNotAllowed(
|
||||||
String user, String domainName, List<MetadataOperation> operations) {
|
String user, String domainName, List<MetadataOperation> operations) {
|
||||||
return String.format(
|
return String.format(
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import static org.openmetadata.common.utils.CommonUtil.nullOrEmpty;
|
|||||||
import static org.openmetadata.schema.type.EventType.ENTITY_CREATED;
|
import static org.openmetadata.schema.type.EventType.ENTITY_CREATED;
|
||||||
import static org.openmetadata.schema.type.MetadataOperation.CREATE;
|
import static org.openmetadata.schema.type.MetadataOperation.CREATE;
|
||||||
import static org.openmetadata.schema.type.MetadataOperation.VIEW_BASIC;
|
import static org.openmetadata.schema.type.MetadataOperation.VIEW_BASIC;
|
||||||
|
import static org.openmetadata.service.security.DefaultAuthorizer.getSubjectContext;
|
||||||
import static org.openmetadata.service.util.EntityUtil.createOrUpdateOperation;
|
import static org.openmetadata.service.util.EntityUtil.createOrUpdateOperation;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -29,6 +30,7 @@ import java.util.Set;
|
|||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
import javax.json.JsonPatch;
|
import javax.json.JsonPatch;
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
@ -42,6 +44,8 @@ import org.openmetadata.schema.type.EntityHistory;
|
|||||||
import org.openmetadata.schema.type.EntityReference;
|
import org.openmetadata.schema.type.EntityReference;
|
||||||
import org.openmetadata.schema.type.Include;
|
import org.openmetadata.schema.type.Include;
|
||||||
import org.openmetadata.schema.type.MetadataOperation;
|
import org.openmetadata.schema.type.MetadataOperation;
|
||||||
|
import org.openmetadata.schema.type.Permission;
|
||||||
|
import org.openmetadata.schema.type.ResourcePermission;
|
||||||
import org.openmetadata.schema.type.api.BulkOperationResult;
|
import org.openmetadata.schema.type.api.BulkOperationResult;
|
||||||
import org.openmetadata.schema.type.csv.CsvImportResult;
|
import org.openmetadata.schema.type.csv.CsvImportResult;
|
||||||
import org.openmetadata.service.Entity;
|
import org.openmetadata.service.Entity;
|
||||||
@ -52,11 +56,13 @@ import org.openmetadata.service.jdbi3.ListFilter;
|
|||||||
import org.openmetadata.service.limits.Limits;
|
import org.openmetadata.service.limits.Limits;
|
||||||
import org.openmetadata.service.search.SearchListFilter;
|
import org.openmetadata.service.search.SearchListFilter;
|
||||||
import org.openmetadata.service.search.SearchSortFilter;
|
import org.openmetadata.service.search.SearchSortFilter;
|
||||||
|
import org.openmetadata.service.security.AuthorizationException;
|
||||||
import org.openmetadata.service.security.Authorizer;
|
import org.openmetadata.service.security.Authorizer;
|
||||||
import org.openmetadata.service.security.policyevaluator.CreateResourceContext;
|
import org.openmetadata.service.security.policyevaluator.CreateResourceContext;
|
||||||
import org.openmetadata.service.security.policyevaluator.OperationContext;
|
import org.openmetadata.service.security.policyevaluator.OperationContext;
|
||||||
import org.openmetadata.service.security.policyevaluator.ResourceContext;
|
import org.openmetadata.service.security.policyevaluator.ResourceContext;
|
||||||
import org.openmetadata.service.security.policyevaluator.ResourceContextInterface;
|
import org.openmetadata.service.security.policyevaluator.ResourceContextInterface;
|
||||||
|
import org.openmetadata.service.security.policyevaluator.SubjectContext;
|
||||||
import org.openmetadata.service.util.AsyncService;
|
import org.openmetadata.service.util.AsyncService;
|
||||||
import org.openmetadata.service.util.BulkAssetsOperationResponse;
|
import org.openmetadata.service.util.BulkAssetsOperationResponse;
|
||||||
import org.openmetadata.service.util.CSVExportResponse;
|
import org.openmetadata.service.util.CSVExportResponse;
|
||||||
@ -418,9 +424,37 @@ public abstract class EntityResource<T extends EntityInterface, K extends Entity
|
|||||||
|
|
||||||
public Response bulkAddToAssetsAsync(
|
public Response bulkAddToAssetsAsync(
|
||||||
SecurityContext securityContext, UUID entityId, BulkAssetsRequestInterface request) {
|
SecurityContext securityContext, UUID entityId, BulkAssetsRequestInterface request) {
|
||||||
OperationContext operationContext =
|
SubjectContext subjectContext = getSubjectContext(securityContext);
|
||||||
new OperationContext(entityType, MetadataOperation.EDIT_ALL);
|
String user = subjectContext.user().getName();
|
||||||
authorizer.authorize(securityContext, operationContext, getResourceContextById(entityId));
|
|
||||||
|
Set<String> editPermissibleResources =
|
||||||
|
authorizer.listPermissions(securityContext, user).stream()
|
||||||
|
.filter(
|
||||||
|
permission ->
|
||||||
|
permission.getPermissions().stream()
|
||||||
|
.anyMatch(
|
||||||
|
perm ->
|
||||||
|
MetadataOperation.EDIT_TAGS.equals(perm.getOperation())
|
||||||
|
&& Permission.Access.ALLOW.equals(perm.getAccess())))
|
||||||
|
.map(ResourcePermission::getResource)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// Validate if all entity types in the request are in the permissible resources
|
||||||
|
List<String> unauthorizedEntityTypes =
|
||||||
|
request.getAssets().stream()
|
||||||
|
.map(EntityReference::getType)
|
||||||
|
.filter(entityType -> !editPermissibleResources.contains(entityType))
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (!unauthorizedEntityTypes.isEmpty()
|
||||||
|
&& !subjectContext.isAdmin()
|
||||||
|
&& !subjectContext.isBot()) {
|
||||||
|
throw new AuthorizationException(
|
||||||
|
CatalogExceptionMessage.resourcePermissionNotAllowed(
|
||||||
|
user, List.of(MetadataOperation.EDIT_TAGS), unauthorizedEntityTypes));
|
||||||
|
}
|
||||||
|
|
||||||
String jobId = UUID.randomUUID().toString();
|
String jobId = UUID.randomUUID().toString();
|
||||||
ExecutorService executorService = AsyncService.getInstance().getExecutorService();
|
ExecutorService executorService = AsyncService.getInstance().getExecutorService();
|
||||||
executorService.submit(
|
executorService.submit(
|
||||||
@ -443,9 +477,34 @@ public abstract class EntityResource<T extends EntityInterface, K extends Entity
|
|||||||
|
|
||||||
public Response bulkRemoveFromAssetsAsync(
|
public Response bulkRemoveFromAssetsAsync(
|
||||||
SecurityContext securityContext, UUID entityId, BulkAssetsRequestInterface request) {
|
SecurityContext securityContext, UUID entityId, BulkAssetsRequestInterface request) {
|
||||||
OperationContext operationContext =
|
SubjectContext subjectContext = getSubjectContext(securityContext);
|
||||||
new OperationContext(entityType, MetadataOperation.EDIT_ALL);
|
String user = subjectContext.user().getName();
|
||||||
authorizer.authorize(securityContext, operationContext, getResourceContextById(entityId));
|
Set<String> editPermissibleResources =
|
||||||
|
authorizer.listPermissions(securityContext, user).stream()
|
||||||
|
.filter(
|
||||||
|
permission ->
|
||||||
|
permission.getPermissions().stream()
|
||||||
|
.anyMatch(
|
||||||
|
perm ->
|
||||||
|
MetadataOperation.EDIT_TAGS.equals(perm.getOperation())
|
||||||
|
&& Permission.Access.ALLOW.equals(perm.getAccess())))
|
||||||
|
.map(ResourcePermission::getResource)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
List<String> unauthorizedEntityTypes =
|
||||||
|
request.getAssets().stream()
|
||||||
|
.map(EntityReference::getType)
|
||||||
|
.filter(entityType -> !editPermissibleResources.contains(entityType))
|
||||||
|
.distinct()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (!unauthorizedEntityTypes.isEmpty()
|
||||||
|
&& !subjectContext.isAdmin()
|
||||||
|
&& !subjectContext.isBot()) {
|
||||||
|
throw new AuthorizationException(
|
||||||
|
CatalogExceptionMessage.resourcePermissionNotAllowed(
|
||||||
|
user, List.of(MetadataOperation.EDIT_TAGS), unauthorizedEntityTypes));
|
||||||
|
}
|
||||||
String jobId = UUID.randomUUID().toString();
|
String jobId = UUID.randomUUID().toString();
|
||||||
ExecutorService executorService = AsyncService.getInstance().getExecutorService();
|
ExecutorService executorService = AsyncService.getInstance().getExecutorService();
|
||||||
executorService.submit(
|
executorService.submit(
|
||||||
|
|||||||
@ -11,28 +11,29 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { expect, Page, test as base } from '@playwright/test';
|
import { expect, Page, test as base } from '@playwright/test';
|
||||||
import { DATA_STEWARD_RULES } from '../../constant/permission';
|
|
||||||
import { PolicyClass } from '../../support/access-control/PoliciesClass';
|
import { PolicyClass } from '../../support/access-control/PoliciesClass';
|
||||||
import { RolesClass } from '../../support/access-control/RolesClass';
|
import { RolesClass } from '../../support/access-control/RolesClass';
|
||||||
import { ClassificationClass } from '../../support/tag/ClassificationClass';
|
import { ClassificationClass } from '../../support/tag/ClassificationClass';
|
||||||
import { TagClass } from '../../support/tag/TagClass';
|
import { TagClass } from '../../support/tag/TagClass';
|
||||||
|
import { TeamClass } from '../../support/team/TeamClass';
|
||||||
import { UserClass } from '../../support/user/UserClass';
|
import { UserClass } from '../../support/user/UserClass';
|
||||||
import { performAdminLogin } from '../../utils/admin';
|
import { performAdminLogin } from '../../utils/admin';
|
||||||
import { redirectToHomePage } from '../../utils/common';
|
import { getApiContext, redirectToHomePage, uuid } from '../../utils/common';
|
||||||
import {
|
import {
|
||||||
addAssetsToTag,
|
addAssetsToTag,
|
||||||
checkAssetsCount,
|
|
||||||
editTagPageDescription,
|
editTagPageDescription,
|
||||||
|
LIMITED_USER_RULES,
|
||||||
removeAssetsFromTag,
|
removeAssetsFromTag,
|
||||||
setupAssetsForTag,
|
setupAssetsForTag,
|
||||||
|
verifyCertificationTagPageUI,
|
||||||
verifyTagPageUI,
|
verifyTagPageUI,
|
||||||
} from '../../utils/tag';
|
} from '../../utils/tag';
|
||||||
|
|
||||||
const adminUser = new UserClass();
|
const adminUser = new UserClass();
|
||||||
const dataConsumerUser = new UserClass();
|
const dataConsumerUser = new UserClass();
|
||||||
const dataStewardUser = new UserClass();
|
const dataStewardUser = new UserClass();
|
||||||
const policy = new PolicyClass();
|
const limitedAccessUser = new UserClass();
|
||||||
const role = new RolesClass();
|
|
||||||
const classification = new ClassificationClass({
|
const classification = new ClassificationClass({
|
||||||
provider: 'system',
|
provider: 'system',
|
||||||
mutuallyExclusive: true,
|
mutuallyExclusive: true,
|
||||||
@ -45,6 +46,7 @@ const test = base.extend<{
|
|||||||
adminPage: Page;
|
adminPage: Page;
|
||||||
dataConsumerPage: Page;
|
dataConsumerPage: Page;
|
||||||
dataStewardPage: Page;
|
dataStewardPage: Page;
|
||||||
|
limitedAccessPage: Page;
|
||||||
}>({
|
}>({
|
||||||
adminPage: async ({ browser }, use) => {
|
adminPage: async ({ browser }, use) => {
|
||||||
const adminPage = await browser.newPage();
|
const adminPage = await browser.newPage();
|
||||||
@ -64,6 +66,12 @@ const test = base.extend<{
|
|||||||
await use(page);
|
await use(page);
|
||||||
await page.close();
|
await page.close();
|
||||||
},
|
},
|
||||||
|
limitedAccessPage: async ({ browser }, use) => {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
await limitedAccessUser.login(page);
|
||||||
|
await use(page);
|
||||||
|
await page.close();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
base.beforeAll('Setup pre-requests', async ({ browser }) => {
|
base.beforeAll('Setup pre-requests', async ({ browser }) => {
|
||||||
@ -73,8 +81,7 @@ base.beforeAll('Setup pre-requests', async ({ browser }) => {
|
|||||||
await dataConsumerUser.create(apiContext);
|
await dataConsumerUser.create(apiContext);
|
||||||
await dataStewardUser.create(apiContext);
|
await dataStewardUser.create(apiContext);
|
||||||
await dataStewardUser.setDataStewardRole(apiContext);
|
await dataStewardUser.setDataStewardRole(apiContext);
|
||||||
await policy.create(apiContext, DATA_STEWARD_RULES);
|
await limitedAccessUser.create(apiContext);
|
||||||
await role.create(apiContext, [policy.responseData.name]);
|
|
||||||
await classification.create(apiContext);
|
await classification.create(apiContext);
|
||||||
await tag.create(apiContext);
|
await tag.create(apiContext);
|
||||||
await afterAction();
|
await afterAction();
|
||||||
@ -85,8 +92,7 @@ base.afterAll('Cleanup', async ({ browser }) => {
|
|||||||
await adminUser.delete(apiContext);
|
await adminUser.delete(apiContext);
|
||||||
await dataConsumerUser.delete(apiContext);
|
await dataConsumerUser.delete(apiContext);
|
||||||
await dataStewardUser.delete(apiContext);
|
await dataStewardUser.delete(apiContext);
|
||||||
await policy.delete(apiContext);
|
await limitedAccessUser.delete(apiContext);
|
||||||
await role.delete(apiContext);
|
|
||||||
await classification.delete(apiContext);
|
await classification.delete(apiContext);
|
||||||
await tag.delete(apiContext);
|
await tag.delete(apiContext);
|
||||||
await afterAction();
|
await afterAction();
|
||||||
@ -99,6 +105,12 @@ test.describe('Tag Page with Admin Roles', () => {
|
|||||||
await verifyTagPageUI(adminPage, classification.data.name, tag);
|
await verifyTagPageUI(adminPage, classification.data.name, tag);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Certification Page should not have Asset button', async ({
|
||||||
|
adminPage,
|
||||||
|
}) => {
|
||||||
|
await verifyCertificationTagPageUI(adminPage);
|
||||||
|
});
|
||||||
|
|
||||||
test('Rename Tag name', async ({ adminPage }) => {
|
test('Rename Tag name', async ({ adminPage }) => {
|
||||||
await redirectToHomePage(adminPage);
|
await redirectToHomePage(adminPage);
|
||||||
const res = adminPage.waitForResponse(`/api/v1/tags/name/*`);
|
const res = adminPage.waitForResponse(`/api/v1/tags/name/*`);
|
||||||
@ -202,22 +214,15 @@ test.describe('Tag Page with Admin Roles', () => {
|
|||||||
|
|
||||||
test('Add and Remove Assets', async ({ adminPage }) => {
|
test('Add and Remove Assets', async ({ adminPage }) => {
|
||||||
await redirectToHomePage(adminPage);
|
await redirectToHomePage(adminPage);
|
||||||
const { assets } = await setupAssetsForTag(adminPage);
|
const { assets, assetCleanup } = await setupAssetsForTag(adminPage);
|
||||||
|
|
||||||
await test.step('Add Asset', async () => {
|
await test.step('Add Asset ', async () => {
|
||||||
const res = adminPage.waitForResponse(`/api/v1/tags/name/*`);
|
await addAssetsToTag(adminPage, assets, tag);
|
||||||
await tag.visitPage(adminPage);
|
|
||||||
await res;
|
|
||||||
await addAssetsToTag(adminPage, assets);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await test.step('Delete Asset', async () => {
|
await test.step('Delete Asset', async () => {
|
||||||
const res = adminPage.waitForResponse(`/api/v1/tags/name/*`);
|
await removeAssetsFromTag(adminPage, assets, tag);
|
||||||
await tag.visitPage(adminPage);
|
await assetCleanup();
|
||||||
await res;
|
|
||||||
|
|
||||||
await removeAssetsFromTag(adminPage, assets);
|
|
||||||
await checkAssetsCount(adminPage, 0);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -234,11 +239,34 @@ test.describe('Tag Page with Data Consumer Roles', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Edit Tag Description or Data Consumer', async ({
|
test('Certification Page should not have Asset button for Data Consumer', async ({
|
||||||
|
dataConsumerPage,
|
||||||
|
}) => {
|
||||||
|
await verifyCertificationTagPageUI(dataConsumerPage);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Edit Tag Description for Data Consumer', async ({
|
||||||
dataConsumerPage,
|
dataConsumerPage,
|
||||||
}) => {
|
}) => {
|
||||||
await editTagPageDescription(dataConsumerPage, tag);
|
await editTagPageDescription(dataConsumerPage, tag);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Add and Remove Assets for Data Consumer', async ({
|
||||||
|
adminPage,
|
||||||
|
dataConsumerPage,
|
||||||
|
}) => {
|
||||||
|
const { assets, assetCleanup } = await setupAssetsForTag(adminPage);
|
||||||
|
await redirectToHomePage(dataConsumerPage);
|
||||||
|
|
||||||
|
await test.step('Add Asset ', async () => {
|
||||||
|
await addAssetsToTag(dataConsumerPage, assets, tag);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Delete Asset', async () => {
|
||||||
|
await removeAssetsFromTag(dataConsumerPage, assets, tag);
|
||||||
|
await assetCleanup();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe('Tag Page with Data Steward Roles', () => {
|
test.describe('Tag Page with Data Steward Roles', () => {
|
||||||
@ -248,7 +276,82 @@ test.describe('Tag Page with Data Steward Roles', () => {
|
|||||||
await verifyTagPageUI(dataStewardPage, classification.data.name, tag, true);
|
await verifyTagPageUI(dataStewardPage, classification.data.name, tag, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Certification Page should not have Asset button for Data Steward', async ({
|
||||||
|
dataStewardPage,
|
||||||
|
}) => {
|
||||||
|
await verifyCertificationTagPageUI(dataStewardPage);
|
||||||
|
});
|
||||||
|
|
||||||
test('Edit Tag Description for Data Steward', async ({ dataStewardPage }) => {
|
test('Edit Tag Description for Data Steward', async ({ dataStewardPage }) => {
|
||||||
await editTagPageDescription(dataStewardPage, tag);
|
await editTagPageDescription(dataStewardPage, tag);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Add and Remove Assets for Data Steward', async ({
|
||||||
|
adminPage,
|
||||||
|
dataStewardPage,
|
||||||
|
}) => {
|
||||||
|
const { assets, assetCleanup } = await setupAssetsForTag(adminPage);
|
||||||
|
await redirectToHomePage(dataStewardPage);
|
||||||
|
|
||||||
|
await test.step('Add Asset ', async () => {
|
||||||
|
await addAssetsToTag(dataStewardPage, assets, tag);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Delete Asset', async () => {
|
||||||
|
await removeAssetsFromTag(dataStewardPage, assets, tag);
|
||||||
|
await assetCleanup();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Tag Page with Limited EditTag Permission', () => {
|
||||||
|
test.slow(true);
|
||||||
|
|
||||||
|
test('Add and Remove Assets and Check Restricted Entity', async ({
|
||||||
|
adminPage,
|
||||||
|
limitedAccessPage,
|
||||||
|
}) => {
|
||||||
|
const { apiContext, afterAction } = await getApiContext(adminPage);
|
||||||
|
const { assets, otherAsset, assetCleanup } = await setupAssetsForTag(
|
||||||
|
adminPage
|
||||||
|
);
|
||||||
|
const id = uuid();
|
||||||
|
const policy = new PolicyClass();
|
||||||
|
const role = new RolesClass();
|
||||||
|
let limitedAccessTeam: TeamClass | null = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await policy.create(apiContext, LIMITED_USER_RULES);
|
||||||
|
await role.create(apiContext, [policy.responseData.name]);
|
||||||
|
|
||||||
|
limitedAccessTeam = new TeamClass({
|
||||||
|
name: `PW%limited_user_access_team-${id}`,
|
||||||
|
displayName: `PW Limited User Access Team ${id}`,
|
||||||
|
description: 'playwright data steward team description',
|
||||||
|
teamType: 'Group',
|
||||||
|
users: [limitedAccessUser.responseData.id],
|
||||||
|
defaultRoles: role.responseData.id ? [role.responseData.id] : [],
|
||||||
|
});
|
||||||
|
await limitedAccessTeam.create(apiContext);
|
||||||
|
|
||||||
|
await redirectToHomePage(limitedAccessPage);
|
||||||
|
|
||||||
|
await test.step('Add Asset ', async () => {
|
||||||
|
await addAssetsToTag(limitedAccessPage, assets, tag, otherAsset);
|
||||||
|
});
|
||||||
|
|
||||||
|
await test.step('Delete Asset', async () => {
|
||||||
|
await removeAssetsFromTag(limitedAccessPage, assets, tag);
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await tag.delete(apiContext);
|
||||||
|
await policy.delete(apiContext);
|
||||||
|
await role.delete(apiContext);
|
||||||
|
if (limitedAccessTeam) {
|
||||||
|
await limitedAccessTeam.delete(apiContext);
|
||||||
|
}
|
||||||
|
await assetCleanup();
|
||||||
|
await afterAction();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -11,10 +11,13 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { expect, Page } from '@playwright/test';
|
import { expect, Page } from '@playwright/test';
|
||||||
import { get } from 'lodash';
|
import { get, isUndefined } from 'lodash';
|
||||||
import { SidebarItem } from '../constant/sidebar';
|
import { SidebarItem } from '../constant/sidebar';
|
||||||
|
import { PolicyRulesType } from '../support/access-control/PoliciesClass';
|
||||||
import { DashboardClass } from '../support/entity/DashboardClass';
|
import { DashboardClass } from '../support/entity/DashboardClass';
|
||||||
import { EntityClass } from '../support/entity/EntityClass';
|
import { EntityClass } from '../support/entity/EntityClass';
|
||||||
|
import { MlModelClass } from '../support/entity/MlModelClass';
|
||||||
|
import { PipelineClass } from '../support/entity/PipelineClass';
|
||||||
import { TableClass } from '../support/entity/TableClass';
|
import { TableClass } from '../support/entity/TableClass';
|
||||||
import { TopicClass } from '../support/entity/TopicClass';
|
import { TopicClass } from '../support/entity/TopicClass';
|
||||||
import { TagClass } from '../support/tag/TagClass';
|
import { TagClass } from '../support/tag/TagClass';
|
||||||
@ -51,20 +54,54 @@ export const visitClassificationPage = async (
|
|||||||
await expect(page.locator('.activeCategory')).toContainText(
|
await expect(page.locator('.activeCategory')).toContainText(
|
||||||
classificationName
|
classificationName
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addAssetsToTag = async (page: Page, assets: EntityClass[]) => {
|
// Other asset type that should not get from the search in explore, they are not added to the tag
|
||||||
|
export const addAssetsToTag = async (
|
||||||
|
page: Page,
|
||||||
|
assets: EntityClass[],
|
||||||
|
tag: TagClass,
|
||||||
|
otherAsset?: EntityClass[]
|
||||||
|
) => {
|
||||||
|
const res = page.waitForResponse(`/api/v1/tags/name/*`);
|
||||||
|
await tag.visitPage(page);
|
||||||
|
await res;
|
||||||
|
|
||||||
await page.getByTestId('assets').click();
|
await page.getByTestId('assets').click();
|
||||||
|
const initialFetchResponse = page.waitForResponse(
|
||||||
|
'/api/v1/search/query?q=&index=all&from=0&size=25&deleted=false**'
|
||||||
|
);
|
||||||
await page.getByTestId('data-classification-add-button').click();
|
await page.getByTestId('data-classification-add-button').click();
|
||||||
|
|
||||||
|
await initialFetchResponse;
|
||||||
|
|
||||||
await expect(page.getByRole('dialog')).toBeVisible();
|
await expect(page.getByRole('dialog')).toBeVisible();
|
||||||
|
|
||||||
|
if (!isUndefined(otherAsset)) {
|
||||||
|
for (const asset of otherAsset) {
|
||||||
|
const name = get(asset, 'entityResponseData.name');
|
||||||
|
|
||||||
|
const searchRes = page.waitForResponse(
|
||||||
|
`/api/v1/search/query?q=${name}&index=all&from=0&size=25&**`
|
||||||
|
);
|
||||||
|
await page
|
||||||
|
.getByTestId('asset-selection-modal')
|
||||||
|
.getByTestId('searchbar')
|
||||||
|
.fill(name);
|
||||||
|
await searchRes;
|
||||||
|
|
||||||
|
await expect(page.getByText(name)).not.toBeVisible();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const asset of assets) {
|
for (const asset of assets) {
|
||||||
const name = get(asset, 'entityResponseData.name');
|
const name = get(asset, 'entityResponseData.name');
|
||||||
const fqn = get(asset, 'entityResponseData.fullyQualifiedName');
|
const fqn = get(asset, 'entityResponseData.fullyQualifiedName');
|
||||||
|
|
||||||
const searchRes = page.waitForResponse(
|
const searchRes = page.waitForResponse(
|
||||||
`/api/v1/search/query?q=${name}&index=all&from=0&size=25&*`
|
`/api/v1/search/query?q=${name}&index=all&from=0&size=25&**`
|
||||||
);
|
);
|
||||||
await page
|
await page
|
||||||
.getByTestId('asset-selection-modal')
|
.getByTestId('asset-selection-modal')
|
||||||
@ -82,8 +119,13 @@ export const addAssetsToTag = async (page: Page, assets: EntityClass[]) => {
|
|||||||
|
|
||||||
export const removeAssetsFromTag = async (
|
export const removeAssetsFromTag = async (
|
||||||
page: Page,
|
page: Page,
|
||||||
assets: EntityClass[]
|
assets: EntityClass[],
|
||||||
|
tag: TagClass
|
||||||
) => {
|
) => {
|
||||||
|
const res = page.waitForResponse(`/api/v1/tags/name/*`);
|
||||||
|
await tag.visitPage(page);
|
||||||
|
await res;
|
||||||
|
|
||||||
await page.getByTestId('assets').click();
|
await page.getByTestId('assets').click();
|
||||||
for (const asset of assets) {
|
for (const asset of assets) {
|
||||||
const fqn = get(asset, 'entityResponseData.fullyQualifiedName');
|
const fqn = get(asset, 'entityResponseData.fullyQualifiedName');
|
||||||
@ -94,6 +136,8 @@ export const removeAssetsFromTag = async (
|
|||||||
|
|
||||||
await page.getByTestId('delete-all-button').click();
|
await page.getByTestId('delete-all-button').click();
|
||||||
await assetsRemoveRes;
|
await assetsRemoveRes;
|
||||||
|
|
||||||
|
await checkAssetsCount(page, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkAssetsCount = async (page: Page, count: number) => {
|
export const checkAssetsCount = async (page: Page, count: number) => {
|
||||||
@ -107,10 +151,14 @@ export const setupAssetsForTag = async (page: Page) => {
|
|||||||
const table = new TableClass();
|
const table = new TableClass();
|
||||||
const topic = new TopicClass();
|
const topic = new TopicClass();
|
||||||
const dashboard = new DashboardClass();
|
const dashboard = new DashboardClass();
|
||||||
|
const mlModel = new MlModelClass();
|
||||||
|
const pipeline = new PipelineClass();
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
table.create(apiContext),
|
table.create(apiContext),
|
||||||
topic.create(apiContext),
|
topic.create(apiContext),
|
||||||
dashboard.create(apiContext),
|
dashboard.create(apiContext),
|
||||||
|
mlModel.create(apiContext),
|
||||||
|
pipeline.create(apiContext),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const assetCleanup = async () => {
|
const assetCleanup = async () => {
|
||||||
@ -118,12 +166,15 @@ export const setupAssetsForTag = async (page: Page) => {
|
|||||||
table.delete(apiContext),
|
table.delete(apiContext),
|
||||||
topic.delete(apiContext),
|
topic.delete(apiContext),
|
||||||
dashboard.delete(apiContext),
|
dashboard.delete(apiContext),
|
||||||
|
mlModel.delete(apiContext),
|
||||||
|
pipeline.delete(apiContext),
|
||||||
]);
|
]);
|
||||||
await afterAction();
|
await afterAction();
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
assets: [table, topic, dashboard],
|
assets: [table, topic, dashboard],
|
||||||
|
otherAsset: [mlModel, pipeline],
|
||||||
assetCleanup,
|
assetCleanup,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -246,17 +297,13 @@ export const verifyTagPageUI = async (
|
|||||||
);
|
);
|
||||||
await expect(page.getByText(tag.data.description)).toBeVisible();
|
await expect(page.getByText(tag.data.description)).toBeVisible();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('data-classification-add-button')
|
||||||
|
).toBeVisible();
|
||||||
|
|
||||||
if (limitedAccess) {
|
if (limitedAccess) {
|
||||||
await expect(
|
|
||||||
page.getByTestId('data-classification-add-button')
|
|
||||||
).not.toBeVisible();
|
|
||||||
await expect(page.getByTestId('manage-button')).not.toBeVisible();
|
await expect(page.getByTestId('manage-button')).not.toBeVisible();
|
||||||
await expect(page.getByTestId('add-domain')).not.toBeVisible();
|
await expect(page.getByTestId('add-domain')).not.toBeVisible();
|
||||||
|
|
||||||
// Asset tab should show no data placeholder and not add asset button
|
|
||||||
await page.getByTestId('assets').click();
|
|
||||||
|
|
||||||
await expect(page.getByTestId('no-data-placeholder')).toBeVisible();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const classificationTable = page.waitForResponse(
|
const classificationTable = page.waitForResponse(
|
||||||
@ -295,3 +342,88 @@ export const editTagPageDescription = async (page: Page, tag: TagClass) => {
|
|||||||
`This is updated test description for tag ${tag.data.name}.`
|
`This is updated test description for tag ${tag.data.name}.`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const verifyCertificationTagPageUI = async (page: Page) => {
|
||||||
|
await redirectToHomePage(page);
|
||||||
|
const res = page.waitForResponse(`/api/v1/tags/name/*`);
|
||||||
|
await visitClassificationPage(page, 'Certification');
|
||||||
|
await page.getByTestId('Gold').click();
|
||||||
|
await res;
|
||||||
|
|
||||||
|
await page.getByTestId('assets').click();
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByTestId('data-classification-add-button')
|
||||||
|
).not.toBeVisible();
|
||||||
|
await expect(page.getByTestId('no-data-placeholder')).toBeVisible();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LIMITED_USER_RULES: PolicyRulesType[] = [
|
||||||
|
{
|
||||||
|
name: 'limitedUserEditTagRole',
|
||||||
|
resources: [
|
||||||
|
'apiCollection',
|
||||||
|
'apiEndpoint',
|
||||||
|
'apiService',
|
||||||
|
'app',
|
||||||
|
'appMarketPlaceDefinition',
|
||||||
|
'bot',
|
||||||
|
'chart',
|
||||||
|
'classification',
|
||||||
|
'container',
|
||||||
|
'dashboardDataModel',
|
||||||
|
'dashboardService',
|
||||||
|
'database',
|
||||||
|
'databaseSchema',
|
||||||
|
'databaseService',
|
||||||
|
'dataInsightChart',
|
||||||
|
'dataInsightCustomChart',
|
||||||
|
'dataInsightDashboard',
|
||||||
|
'dataProduct',
|
||||||
|
'document',
|
||||||
|
'domain',
|
||||||
|
'entityReportData',
|
||||||
|
'eventsubscription',
|
||||||
|
'feed',
|
||||||
|
'glossary',
|
||||||
|
'glossaryTerm',
|
||||||
|
'ingestionPipeline',
|
||||||
|
'kpi',
|
||||||
|
'messagingService',
|
||||||
|
'metadataService',
|
||||||
|
'metric',
|
||||||
|
'mlmodel',
|
||||||
|
'mlmodelService',
|
||||||
|
'page',
|
||||||
|
'persona',
|
||||||
|
'pipeline',
|
||||||
|
'pipelineService',
|
||||||
|
'policy',
|
||||||
|
'query',
|
||||||
|
'report',
|
||||||
|
'role',
|
||||||
|
'searchIndex',
|
||||||
|
'searchService',
|
||||||
|
'storageService',
|
||||||
|
'storedProcedure',
|
||||||
|
'suggestion',
|
||||||
|
'tag',
|
||||||
|
'team',
|
||||||
|
'testCase',
|
||||||
|
'testCaseResolutionStatus',
|
||||||
|
'testCaseResult',
|
||||||
|
'testConnectionDefinition',
|
||||||
|
'testDefinition',
|
||||||
|
'testSuite',
|
||||||
|
'type',
|
||||||
|
'user',
|
||||||
|
'webAnalyticEvent',
|
||||||
|
'workflow',
|
||||||
|
'workflowDefinition',
|
||||||
|
'workflowInstance',
|
||||||
|
'workflowInstanceState',
|
||||||
|
],
|
||||||
|
operations: ['EditTags'],
|
||||||
|
effect: 'deny',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|||||||
@ -30,7 +30,7 @@ export const getTabs = (
|
|||||||
},
|
},
|
||||||
users: {
|
users: {
|
||||||
name: t('label.user-plural'),
|
name: t('label.user-plural'),
|
||||||
count: currentTeam.userCount ?? 0,
|
count: currentTeam.users?.length ?? 0,
|
||||||
key: TeamsPageTab.USERS,
|
key: TeamsPageTab.USERS,
|
||||||
},
|
},
|
||||||
assets: {
|
assets: {
|
||||||
|
|||||||
@ -94,7 +94,6 @@ export const UserTab = ({
|
|||||||
showPagination,
|
showPagination,
|
||||||
} = usePaging(PAGE_SIZE_MEDIUM);
|
} = usePaging(PAGE_SIZE_MEDIUM);
|
||||||
|
|
||||||
|
|
||||||
const usersList = useMemo(() => {
|
const usersList = useMemo(() => {
|
||||||
return users.map((item) =>
|
return users.map((item) =>
|
||||||
getEntityReferenceFromEntity(item, EntityType.USER)
|
getEntityReferenceFromEntity(item, EntityType.USER)
|
||||||
@ -379,7 +378,7 @@ export const UserTab = ({
|
|||||||
onSearch={handleUsersSearchAction}
|
onSearch={handleUsersSearchAction}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
{!currentTeam.deleted && (
|
{!currentTeam.deleted && isGroupType && (
|
||||||
<Col>
|
<Col>
|
||||||
<Space>
|
<Space>
|
||||||
{users.length > 0 && permission.EditAll && (
|
{users.length > 0 && permission.EditAll && (
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export interface EsTermQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type EsTermsQuery = {
|
export type EsTermsQuery = {
|
||||||
[property: string]: string;
|
[property: string]: string | string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface EsExistsQuery {
|
export interface EsExistsQuery {
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import {
|
|||||||
import { ItemType } from 'antd/lib/menu/hooks/useItems';
|
import { ItemType } from 'antd/lib/menu/hooks/useItems';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { compare } from 'fast-json-patch';
|
import { compare } from 'fast-json-patch';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep, isEmpty, startsWith } from 'lodash';
|
||||||
import React, {
|
import React, {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
@ -92,7 +92,8 @@ import {
|
|||||||
getEncodedFqn,
|
getEncodedFqn,
|
||||||
} from '../../utils/StringsUtils';
|
} from '../../utils/StringsUtils';
|
||||||
import {
|
import {
|
||||||
getQueryFilterToExcludeTerms,
|
getExcludedIndexesBasedOnEntityTypeEditTagPermission,
|
||||||
|
getQueryFilterToExcludeTermsAndEntities,
|
||||||
getTagAssetsQueryFilter,
|
getTagAssetsQueryFilter,
|
||||||
} from '../../utils/TagsUtils';
|
} from '../../utils/TagsUtils';
|
||||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||||
@ -104,7 +105,7 @@ const TagPage = () => {
|
|||||||
const { fqn: tagFqn } = useFqn();
|
const { fqn: tagFqn } = useFqn();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { tab: activeTab = TagTabs.OVERVIEW } = useParams<{ tab?: string }>();
|
const { tab: activeTab = TagTabs.OVERVIEW } = useParams<{ tab?: string }>();
|
||||||
const { getEntityPermission } = usePermissionProvider();
|
const { permissions, getEntityPermission } = usePermissionProvider();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [tagItem, setTagItem] = useState<Tag>();
|
const [tagItem, setTagItem] = useState<Tag>();
|
||||||
const [assetModalVisible, setAssetModalVisible] = useState(false);
|
const [assetModalVisible, setAssetModalVisible] = useState(false);
|
||||||
@ -161,6 +162,23 @@ const TagPage = () => {
|
|||||||
return { editTagsPermission: false, editDescriptionPermission: false };
|
return { editTagsPermission: false, editDescriptionPermission: false };
|
||||||
}, [tagPermissions, tagItem?.deleted]);
|
}, [tagPermissions, tagItem?.deleted]);
|
||||||
|
|
||||||
|
const editEntitiesTagPermission = useMemo(
|
||||||
|
() => getExcludedIndexesBasedOnEntityTypeEditTagPermission(permissions),
|
||||||
|
[permissions]
|
||||||
|
);
|
||||||
|
|
||||||
|
const haveAssetEditPermission = useMemo(
|
||||||
|
() =>
|
||||||
|
editTagsPermission ||
|
||||||
|
!isEmpty(editEntitiesTagPermission.entitiesHavingPermission),
|
||||||
|
[editTagsPermission, editEntitiesTagPermission.entitiesHavingPermission]
|
||||||
|
);
|
||||||
|
|
||||||
|
const isCertificationClassification = useMemo(
|
||||||
|
() => startsWith(tagFqn, 'Certification.'),
|
||||||
|
[tagFqn]
|
||||||
|
);
|
||||||
|
|
||||||
const fetchCurrentTagPermission = async () => {
|
const fetchCurrentTagPermission = async () => {
|
||||||
if (!tagItem?.id) {
|
if (!tagItem?.id) {
|
||||||
return;
|
return;
|
||||||
@ -477,7 +495,16 @@ const TagPage = () => {
|
|||||||
assetCount={assetCount}
|
assetCount={assetCount}
|
||||||
entityFqn={tagItem?.fullyQualifiedName ?? ''}
|
entityFqn={tagItem?.fullyQualifiedName ?? ''}
|
||||||
isSummaryPanelOpen={Boolean(previewAsset)}
|
isSummaryPanelOpen={Boolean(previewAsset)}
|
||||||
permissions={tagPermissions}
|
permissions={
|
||||||
|
{
|
||||||
|
Create:
|
||||||
|
haveAssetEditPermission &&
|
||||||
|
!isCertificationClassification,
|
||||||
|
EditAll:
|
||||||
|
haveAssetEditPermission &&
|
||||||
|
!isCertificationClassification,
|
||||||
|
} as OperationPermission
|
||||||
|
}
|
||||||
ref={assetTabRef}
|
ref={assetTabRef}
|
||||||
type={AssetsOfEntity.TAG}
|
type={AssetsOfEntity.TAG}
|
||||||
onAddAsset={() => setAssetModalVisible(true)}
|
onAddAsset={() => setAssetModalVisible(true)}
|
||||||
@ -591,17 +618,19 @@ const TagPage = () => {
|
|||||||
titleColor={tagItem.style?.color ?? BLACK_COLOR}
|
titleColor={tagItem.style?.color ?? BLACK_COLOR}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
{editTagsPermission && (
|
{haveAssetEditPermission && (
|
||||||
<Col className="p-x-md">
|
<Col className="p-x-md">
|
||||||
<div className="d-flex self-end">
|
<div className="d-flex self-end">
|
||||||
<Button
|
{!isCertificationClassification && (
|
||||||
data-testid="data-classification-add-button"
|
<Button
|
||||||
type="primary"
|
data-testid="data-classification-add-button"
|
||||||
onClick={() => setAssetModalVisible(true)}>
|
type="primary"
|
||||||
{t('label.add-entity', {
|
onClick={() => setAssetModalVisible(true)}>
|
||||||
entity: t('label.asset-plural'),
|
{t('label.add-entity', {
|
||||||
})}
|
entity: t('label.asset-plural'),
|
||||||
</Button>
|
})}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
{manageButtonContent.length > 0 && (
|
{manageButtonContent.length > 0 && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
align={{ targetOffset: [-12, 0] }}
|
align={{ targetOffset: [-12, 0] }}
|
||||||
@ -687,7 +716,10 @@ const TagPage = () => {
|
|||||||
<AssetSelectionModal
|
<AssetSelectionModal
|
||||||
entityFqn={tagItem.fullyQualifiedName}
|
entityFqn={tagItem.fullyQualifiedName}
|
||||||
open={assetModalVisible}
|
open={assetModalVisible}
|
||||||
queryFilter={getQueryFilterToExcludeTerms(tagItem.fullyQualifiedName)}
|
queryFilter={getQueryFilterToExcludeTermsAndEntities(
|
||||||
|
tagItem.fullyQualifiedName,
|
||||||
|
editEntitiesTagPermission.entitiesNotHavingPermission
|
||||||
|
)}
|
||||||
type={AssetsOfEntity.TAG}
|
type={AssetsOfEntity.TAG}
|
||||||
onCancel={() => setAssetModalVisible(false)}
|
onCancel={() => setAssetModalVisible(false)}
|
||||||
onSave={handleAssetSave}
|
onSave={handleAssetSave}
|
||||||
|
|||||||
@ -52,6 +52,31 @@ export const checkPermission = (
|
|||||||
return hasPermission;
|
return hasPermission;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param operation operation like Edit, Delete
|
||||||
|
* @param resourceType Resource type like "bot", "table"
|
||||||
|
* @param permissions UIPermission
|
||||||
|
* @param checkEditAllPermission boolean to check EditALL permission as well
|
||||||
|
* @returns boolean - true/false
|
||||||
|
*/
|
||||||
|
export const checkPermissionEntityResource = (
|
||||||
|
operation: Operation,
|
||||||
|
resourceType: ResourceEntity,
|
||||||
|
permissions: UIPermission,
|
||||||
|
checkEditAllPermission = false
|
||||||
|
) => {
|
||||||
|
const entityResource = permissions?.[resourceType];
|
||||||
|
let hasPermission = entityResource && entityResource[operation];
|
||||||
|
|
||||||
|
if (checkEditAllPermission) {
|
||||||
|
hasPermission =
|
||||||
|
hasPermission || (entityResource && entityResource[Operation.EditAll]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasPermission;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param permission ResourcePermission
|
* @param permission ResourcePermission
|
||||||
|
|||||||
@ -24,6 +24,10 @@ import Loader from '../components/common/Loader/Loader';
|
|||||||
import RichTextEditorPreviewer from '../components/common/RichTextEditor/RichTextEditorPreviewer';
|
import RichTextEditorPreviewer from '../components/common/RichTextEditor/RichTextEditorPreviewer';
|
||||||
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
|
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
|
||||||
import { getExplorePath } from '../constants/constants';
|
import { getExplorePath } from '../constants/constants';
|
||||||
|
import {
|
||||||
|
ResourceEntity,
|
||||||
|
UIPermission,
|
||||||
|
} from '../context/PermissionProvider/PermissionProvider.interface';
|
||||||
import { SettledStatus } from '../enums/Axios.enum';
|
import { SettledStatus } from '../enums/Axios.enum';
|
||||||
import { EntityType } from '../enums/entity.enum';
|
import { EntityType } from '../enums/entity.enum';
|
||||||
import { ExplorePageTabs } from '../enums/Explore.enum';
|
import { ExplorePageTabs } from '../enums/Explore.enum';
|
||||||
@ -32,6 +36,7 @@ import { Classification } from '../generated/entity/classification/classificatio
|
|||||||
import { Tag } from '../generated/entity/classification/tag';
|
import { Tag } from '../generated/entity/classification/tag';
|
||||||
import { GlossaryTerm } from '../generated/entity/data/glossaryTerm';
|
import { GlossaryTerm } from '../generated/entity/data/glossaryTerm';
|
||||||
import { Column } from '../generated/entity/data/table';
|
import { Column } from '../generated/entity/data/table';
|
||||||
|
import { Operation } from '../generated/entity/policies/policy';
|
||||||
import { Paging } from '../generated/type/paging';
|
import { Paging } from '../generated/type/paging';
|
||||||
import { LabelType, State, TagLabel } from '../generated/type/tagLabel';
|
import { LabelType, State, TagLabel } from '../generated/type/tagLabel';
|
||||||
import { searchQuery } from '../rest/searchAPI';
|
import { searchQuery } from '../rest/searchAPI';
|
||||||
@ -41,6 +46,7 @@ import {
|
|||||||
getTags,
|
getTags,
|
||||||
} from '../rest/tagAPI';
|
} from '../rest/tagAPI';
|
||||||
import { getQueryFilterToIncludeApprovedTerm } from './GlossaryUtils';
|
import { getQueryFilterToIncludeApprovedTerm } from './GlossaryUtils';
|
||||||
|
import { checkPermissionEntityResource } from './PermissionsUtils';
|
||||||
import { getTagsWithoutTier } from './TableUtils';
|
import { getTagsWithoutTier } from './TableUtils';
|
||||||
|
|
||||||
export const getClassifications = async (
|
export const getClassifications = async (
|
||||||
@ -318,7 +324,10 @@ export const createTagObject = (tags: EntityTags[]) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getQueryFilterToExcludeTerms = (fqn: string) => ({
|
export const getQueryFilterToExcludeTermsAndEntities = (
|
||||||
|
fqn: string,
|
||||||
|
excludeEntityIndex: string[] = []
|
||||||
|
) => ({
|
||||||
query: {
|
query: {
|
||||||
bool: {
|
bool: {
|
||||||
must: [
|
must: [
|
||||||
@ -337,13 +346,17 @@ export const getQueryFilterToExcludeTerms = (fqn: string) => ({
|
|||||||
bool: {
|
bool: {
|
||||||
must_not: [
|
must_not: [
|
||||||
{
|
{
|
||||||
term: {
|
terms: {
|
||||||
entityType: EntityType.TAG,
|
entityType: [
|
||||||
},
|
EntityType.CLASSIFICATION,
|
||||||
},
|
EntityType.TEST_SUITE,
|
||||||
{
|
EntityType.TEST_CASE,
|
||||||
term: {
|
EntityType.TEST_CASE_RESOLUTION_STATUS,
|
||||||
entityType: EntityType.DATA_PRODUCT,
|
EntityType.TEST_CASE_RESULT,
|
||||||
|
EntityType.TAG,
|
||||||
|
EntityType.DATA_PRODUCT,
|
||||||
|
...excludeEntityIndex,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -354,6 +367,185 @@ export const getQueryFilterToExcludeTerms = (fqn: string) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const getExcludedIndexesBasedOnEntityTypeEditTagPermission = (
|
||||||
|
permissions: UIPermission
|
||||||
|
) => {
|
||||||
|
const entityPermission = {
|
||||||
|
[EntityType.TABLE]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.TABLE,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.TOPIC]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.TOPIC,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.DASHBOARD]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.DASHBOARD,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.MLMODEL]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.ML_MODEL,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.PIPELINE]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.PIPELINE,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.CONTAINER]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.CONTAINER,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.SEARCH_INDEX]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.SEARCH_INDEX,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.API_SERVICE]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.API_SERVICE,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.API_ENDPOINT]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.API_ENDPOINT,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.API_COLLECTION]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.API_COLLECTION,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.DASHBOARD_DATA_MODEL]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.DASHBOARD_DATA_MODEL,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.STORED_PROCEDURE]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.STORED_PROCEDURE,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.DATABASE]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.DATABASE,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.DATABASE_SERVICE]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.DATABASE_SERVICE,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.DATABASE_SCHEMA]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.DATABASE_SCHEMA,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.MESSAGING_SERVICE]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.PIPELINE_SERVICE,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.DASHBOARD_SERVICE]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.DASHBOARD_SERVICE,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.MLMODEL_SERVICE]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.ML_MODEL_SERVICE,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.PIPELINE_SERVICE]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.PIPELINE_SERVICE,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.STORAGE_SERVICE]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.STORAGE_SERVICE,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.SEARCH_SERVICE]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.SEARCH_SERVICE,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.GLOSSARY]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.GLOSSARY,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.GLOSSARY_TERM]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.GLOSSARY_TERM,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
[EntityType.DOMAIN]: checkPermissionEntityResource(
|
||||||
|
Operation.EditTags,
|
||||||
|
ResourceEntity.DOMAIN,
|
||||||
|
permissions,
|
||||||
|
true
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (Object.keys(entityPermission) as EntityType[]).reduce(
|
||||||
|
(
|
||||||
|
acc: {
|
||||||
|
entitiesHavingPermission: EntityType[];
|
||||||
|
entitiesNotHavingPermission: EntityType[];
|
||||||
|
},
|
||||||
|
cv: EntityType
|
||||||
|
) => {
|
||||||
|
const currentEntityPermission =
|
||||||
|
entityPermission[cv as keyof typeof entityPermission];
|
||||||
|
if (currentEntityPermission) {
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
entitiesHavingPermission: [...acc.entitiesHavingPermission, cv],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...acc,
|
||||||
|
entitiesNotHavingPermission: [...acc.entitiesNotHavingPermission, cv],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entitiesHavingPermission: [],
|
||||||
|
entitiesNotHavingPermission: [],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const getTagAssetsQueryFilter = (fqn: string) => {
|
export const getTagAssetsQueryFilter = (fqn: string) => {
|
||||||
if (fqn.includes('Tier.')) {
|
if (fqn.includes('Tier.')) {
|
||||||
return `(tier.tagFQN:"${fqn}")`;
|
return `(tier.tagFQN:"${fqn}")`;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user