From 57a4ec380f8e4dabd084fcb3582f0c07ad3cdc10 Mon Sep 17 00:00:00 2001 From: Anatolii Yemets <106543964+anatolii-yemets@users.noreply.github.com> Date: Wed, 21 Feb 2024 16:11:58 +0100 Subject: [PATCH] Create new Query Example permission policy (#1633) Co-authored-by: vburlachenko Co-authored-by: ayemets-corcentric <106543964+ayemets-corcentric@users.noreply.github.com> --- .../extractor/URLResourceExtractor.java | 1 + .../auth/util/SecurityConstants.java | 13 +- .../controller/DataEntityController.java | 23 ++++ .../controller/QueryExampleController.java | 20 --- .../oddplatform/dto/QueryExampleDto.java | 3 +- .../dto/policy/PolicyConditionKeyDto.java | 3 +- .../dto/policy/PolicyPermissionDto.java | 9 +- .../oddplatform/dto/policy/PolicyTypeDto.java | 1 + .../QueryExamplePolicyResolverContext.java | 7 + .../service/QueryExampleService.java | 4 +- .../service/QueryExampleServiceImpl.java | 6 +- .../QueryExamplePermissionExtractor.java | 60 ++++++++ .../policy/PolicyPermissionExtractor.java | 9 ++ .../QueryExampleComparorFactory.java | 15 ++ .../QueryExampleFieldComparer.java | 16 +++ .../QueryExampleConditionResolver.java | 24 ++++ .../V0_0_88__add_query_example_policy.sql | 11 ++ .../main/resources/schema/policy_schema.json | 130 +++++++++++++++++- .../service/QueryExampleServiceTest.java | 13 +- odd-platform-specification/components.yaml | 7 +- odd-platform-specification/openapi.yaml | 74 +++++----- .../DataEntityDetailsRoutes.tsx | 2 +- .../AssignEntityQueryExampleForm.tsx | 4 +- .../QueryExampleDetailsContainer.tsx | 86 ++++-------- .../QueryExampleDetailsContainerActions.tsx | 82 +++++++++++ .../QueryExamplesAutocomplete.tsx | 16 ++- .../hooks/api/dataModelling/queryExamples.ts | 29 ++-- .../src/lib/hooks/api/permissions.ts | 12 ++ 28 files changed, 513 insertions(+), 167 deletions(-) create mode 100644 odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/QueryExamplePolicyResolverContext.java create mode 100644 odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/permission/extractor/QueryExamplePermissionExtractor.java create mode 100644 odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/comparer/queryexample/QueryExampleComparorFactory.java create mode 100644 odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/comparer/queryexample/QueryExampleFieldComparer.java create mode 100644 odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/resolver/QueryExampleConditionResolver.java create mode 100644 odd-platform-api/src/main/resources/db/migration/V0_0_88__add_query_example_policy.sql create mode 100644 odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsContainerActions.tsx create mode 100644 odd-platform-ui/src/lib/hooks/api/permissions.ts diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/auth/manager/extractor/URLResourceExtractor.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/auth/manager/extractor/URLResourceExtractor.java index d81b42ff1..6d12d9652 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/auth/manager/extractor/URLResourceExtractor.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/auth/manager/extractor/URLResourceExtractor.java @@ -11,6 +11,7 @@ public class URLResourceExtractor implements ResourceExtractor { @Override public boolean handles(final AuthorizationManagerType type) { return type == AuthorizationManagerType.DATA_ENTITY || type == AuthorizationManagerType.TERM + || type == AuthorizationManagerType.QUERY_EXAMPLE || type == AuthorizationManagerType.DEG; } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/auth/util/SecurityConstants.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/auth/util/SecurityConstants.java index cd38a8d4a..b623ace3b 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/auth/util/SecurityConstants.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/auth/util/SecurityConstants.java @@ -9,6 +9,7 @@ import static org.opendatadiscovery.oddplatform.auth.manager.AuthorizationManagerType.DATASET_FIELD; import static org.opendatadiscovery.oddplatform.auth.manager.AuthorizationManagerType.DATA_ENTITY; import static org.opendatadiscovery.oddplatform.auth.manager.AuthorizationManagerType.NO_CONTEXT; +import static org.opendatadiscovery.oddplatform.auth.manager.AuthorizationManagerType.QUERY_EXAMPLE; import static org.opendatadiscovery.oddplatform.auth.manager.AuthorizationManagerType.TERM; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.COLLECTOR_CREATE; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.COLLECTOR_DELETE; @@ -290,18 +291,18 @@ DATA_ENTITY, new PathPatternParserServerWebExchangeMatcher( AuthorizationManagerType.DEG, new PathPatternParserServerWebExchangeMatcher("/api/dataentitygroups/{data_entity_group_id}", PUT), DATA_ENTITY_GROUP_UPDATE), - new SecurityRule(NO_CONTEXT, + new SecurityRule(QUERY_EXAMPLE, new PathPatternParserServerWebExchangeMatcher("/api/queryexample/{example_id}", PUT), QUERY_EXAMPLE_UPDATE), - new SecurityRule(NO_CONTEXT, + new SecurityRule(QUERY_EXAMPLE, new PathPatternParserServerWebExchangeMatcher("/api/queryexample/{example_id}", DELETE), QUERY_EXAMPLE_DELETE), - new SecurityRule(NO_CONTEXT, - new PathPatternParserServerWebExchangeMatcher("/api/queryexample/{example_id}/dataset", POST), + new SecurityRule(DATA_ENTITY, + new PathPatternParserServerWebExchangeMatcher("/api/dataentities/{data_entity_id}/queryexample", POST), QUERY_EXAMPLE_DATASET_CREATE), - new SecurityRule(NO_CONTEXT, + new SecurityRule(DATA_ENTITY, new PathPatternParserServerWebExchangeMatcher( - "/api/queryexample/{example_id}/dataset/{data_entity_id}", DELETE), + "/api/dataentities/{data_entity_id}/queryexample/{example_id}", DELETE), QUERY_EXAMPLE_DATASET_DELETE), new SecurityRule(NO_CONTEXT, new PathPatternParserServerWebExchangeMatcher("/api/referencedata/table/{lookup_table_id}", PUT), diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/DataEntityController.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/DataEntityController.java index 06ae6fca7..6342f562c 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/DataEntityController.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/DataEntityController.java @@ -21,6 +21,7 @@ import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityGroupLineageList; import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityLineage; import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityList; +import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityQueryExampleFormData; import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityRef; import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityStatus; import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityStatusFormData; @@ -41,6 +42,7 @@ import org.opendatadiscovery.oddplatform.api.contract.model.Ownership; import org.opendatadiscovery.oddplatform.api.contract.model.OwnershipFormData; import org.opendatadiscovery.oddplatform.api.contract.model.OwnershipUpdateFormData; +import org.opendatadiscovery.oddplatform.api.contract.model.QueryExample; import org.opendatadiscovery.oddplatform.api.contract.model.Tag; import org.opendatadiscovery.oddplatform.api.contract.model.TagsFormData; import org.opendatadiscovery.oddplatform.dto.alert.AlertStatusEnum; @@ -53,6 +55,7 @@ import org.opendatadiscovery.oddplatform.service.MessageService; import org.opendatadiscovery.oddplatform.service.MetricService; import org.opendatadiscovery.oddplatform.service.OwnershipService; +import org.opendatadiscovery.oddplatform.service.QueryExampleService; import org.opendatadiscovery.oddplatform.service.activity.ActivityService; import org.opendatadiscovery.oddplatform.service.term.TermService; import org.springframework.http.ResponseEntity; @@ -75,6 +78,7 @@ public class DataEntityController implements DataEntityApi { private final MessageService messageService; private final AlertHaltConfigService alertHaltConfigService; private final MetricService metricService; + private final QueryExampleService queryExampleService; @Override public Mono> createDataEntityGroup(final Mono formData, @@ -428,4 +432,23 @@ public Mono> getDomains(final ServerWebExch return dataEntityService.getDomainsInfo() .map(ResponseEntity::ok); } + + @Override + public Mono> + createQueryExampleToDatasetRelationshipNew(final Long dataEntityId, + final Mono formDataMono, + final ServerWebExchange exchange) { + return formDataMono + .flatMap(item -> + queryExampleService.createQueryExampleToDatasetRelationship(item.getQueryExampleId(), dataEntityId)) + .map(ResponseEntity::ok); + } + + @Override + public Mono> deleteQueryExampleToDatasetRelationshipNew(final Long dataEntityId, + final Long exampleId, + final ServerWebExchange exchange) { + return queryExampleService.deleteQueryExampleDatasetRelationship(exampleId, dataEntityId) + .thenReturn(ResponseEntity.noContent().build()); + } } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/QueryExampleController.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/QueryExampleController.java index 7e293b140..a6e2ff57d 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/QueryExampleController.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/QueryExampleController.java @@ -3,8 +3,6 @@ import java.util.UUID; import lombok.RequiredArgsConstructor; import org.opendatadiscovery.oddplatform.api.contract.api.QueryExampleApi; -import org.opendatadiscovery.oddplatform.api.contract.model.QueryExample; -import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleDatasetFormData; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleDetails; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleFormData; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleList; @@ -50,24 +48,6 @@ public Mono> deleteQueryExample(final Long exampleId, .thenReturn(ResponseEntity.noContent().build()); } - @Override - public Mono> - createQueryExampleToDatasetRelationship(final Long exampleId, - final Mono queryExampleDatasetFormData, - final ServerWebExchange exchange) { - return queryExampleDatasetFormData - .flatMap(item -> queryExampleService.createQueryExampleToDatasetRelationship(exampleId, item)) - .map(ResponseEntity::ok); - } - - @Override - public Mono> deleteQueryExampleToDatasetRelationship(final Long exampleId, - final Long dataEntityId, - final ServerWebExchange exchange) { - return queryExampleService.deleteQueryExampleDatasetRelationship(exampleId, dataEntityId) - .thenReturn(ResponseEntity.noContent().build()); - } - @Override public Mono> getQueryExampleByDatasetId(final Long dataEntityId, final ServerWebExchange exchange) { diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/QueryExampleDto.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/QueryExampleDto.java index 639786631..1c2f8f12e 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/QueryExampleDto.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/QueryExampleDto.java @@ -4,5 +4,6 @@ import org.opendatadiscovery.oddplatform.model.tables.pojos.DataEntityPojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.QueryExamplePojo; -public record QueryExampleDto(QueryExamplePojo queryExamplePojo, List linkedEntities) { +public record QueryExampleDto(QueryExamplePojo queryExamplePojo, + List linkedEntities) { } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyConditionKeyDto.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyConditionKeyDto.java index 53b5c6699..14d43b182 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyConditionKeyDto.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyConditionKeyDto.java @@ -23,7 +23,8 @@ public enum PolicyConditionKeyDto { TERM_NAMESPACE_NAME("term:namespace:name"), TERM_TAG_NAME("term:tag:name"), TERM_OWNER("term:owner"), - TERM_OWNER_TITLE("term:owner:title"); + TERM_OWNER_TITLE("term:owner:title"), + QUERY_EXAMPLE_NAME("queryexample:name"); private final String value; diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyPermissionDto.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyPermissionDto.java index 904f4a959..d7cbca446 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyPermissionDto.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyPermissionDto.java @@ -5,6 +5,7 @@ import static org.opendatadiscovery.oddplatform.dto.policy.PolicyTypeDto.DATA_ENTITY; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyTypeDto.MANAGEMENT; +import static org.opendatadiscovery.oddplatform.dto.policy.PolicyTypeDto.QUERY_EXAMPLE; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyTypeDto.TERM; @RequiredArgsConstructor @@ -36,6 +37,8 @@ public enum PolicyPermissionDto { DATA_ENTITY_GROUP_UPDATE(DATA_ENTITY), DATA_ENTITY_ATTACHMENT_MANAGE(DATA_ENTITY), DATA_ENTITY_STATUS_UPDATE(DATA_ENTITY), + QUERY_EXAMPLE_DATASET_CREATE(DATA_ENTITY), + QUERY_EXAMPLE_DATASET_DELETE(DATA_ENTITY), TERM_CREATE(MANAGEMENT), TERM_UPDATE(TERM), TERM_DELETE(TERM), @@ -69,10 +72,8 @@ public enum PolicyPermissionDto { ROLE_UPDATE(MANAGEMENT), ROLE_DELETE(MANAGEMENT), QUERY_EXAMPLE_CREATE(MANAGEMENT), - QUERY_EXAMPLE_UPDATE(MANAGEMENT), - QUERY_EXAMPLE_DELETE(MANAGEMENT), - QUERY_EXAMPLE_DATASET_CREATE(MANAGEMENT), - QUERY_EXAMPLE_DATASET_DELETE(MANAGEMENT), + QUERY_EXAMPLE_UPDATE(QUERY_EXAMPLE), + QUERY_EXAMPLE_DELETE(QUERY_EXAMPLE), LOOKUP_TABLE_CREATE(MANAGEMENT), LOOKUP_TABLE_UPDATE(MANAGEMENT), LOOKUP_TABLE_DELETE(MANAGEMENT), diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyTypeDto.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyTypeDto.java index f8e620f09..ffc1e6d4f 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyTypeDto.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/PolicyTypeDto.java @@ -8,6 +8,7 @@ public enum PolicyTypeDto { DATA_ENTITY(true), TERM(true), + QUERY_EXAMPLE(true), MANAGEMENT(false); private final boolean hasContext; diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/QueryExamplePolicyResolverContext.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/QueryExamplePolicyResolverContext.java new file mode 100644 index 000000000..36d542956 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/policy/QueryExamplePolicyResolverContext.java @@ -0,0 +1,7 @@ +package org.opendatadiscovery.oddplatform.dto.policy; + +import org.opendatadiscovery.oddplatform.dto.QueryExampleDto; +import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerPojo; + +public record QueryExamplePolicyResolverContext(QueryExampleDto detailsDto, OwnerPojo currentOwner) { +} \ No newline at end of file diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/QueryExampleService.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/QueryExampleService.java index f8d4f5cf4..7b9fcdda0 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/QueryExampleService.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/QueryExampleService.java @@ -1,7 +1,6 @@ package org.opendatadiscovery.oddplatform.service; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExample; -import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleDatasetFormData; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleDetails; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleFormData; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleList; @@ -14,8 +13,7 @@ public interface QueryExampleService { Mono updateQueryExample(final Long exampleId, final QueryExampleFormData formData); Mono createQueryExampleToDatasetRelationship( - final Long queryExampleId, - final QueryExampleDatasetFormData queryExampleDatasetFormData); + final Long queryExampleId, final Long datasetId); Mono deleteQueryExampleDatasetRelationship(final Long exampleId, final Long dataEntityId); diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/QueryExampleServiceImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/QueryExampleServiceImpl.java index c81c56f35..be5f25703 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/QueryExampleServiceImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/QueryExampleServiceImpl.java @@ -7,7 +7,6 @@ import lombok.RequiredArgsConstructor; import org.opendatadiscovery.oddplatform.annotation.ReactiveTransactional; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExample; -import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleDatasetFormData; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleDetails; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleFormData; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleList; @@ -55,10 +54,9 @@ public Mono updateQueryExample(final Long exampleId, final @Override @ReactiveTransactional public Mono createQueryExampleToDatasetRelationship( - final Long queryExampleId, - final QueryExampleDatasetFormData queryExampleDatasetFormData) { + final Long queryExampleId, final Long datasetId) { return dataEntityToQueryExampleRepository - .createRelationWithDataEntity(queryExampleDatasetFormData.getDatasetId(), queryExampleId) + .createRelationWithDataEntity(datasetId, queryExampleId) .switchIfEmpty(Mono.error(() -> new BadUserRequestException("Dataset assigned to Query Example"))) .then(dataEntityToQueryExampleRepository.getQueryExampleDatasetRelations(queryExampleId)) .map(dto -> queryExampleMapper.mapToQueryExample( diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/permission/extractor/QueryExamplePermissionExtractor.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/permission/extractor/QueryExamplePermissionExtractor.java new file mode 100644 index 000000000..ae4bd9b70 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/permission/extractor/QueryExamplePermissionExtractor.java @@ -0,0 +1,60 @@ +package org.opendatadiscovery.oddplatform.service.permission.extractor; + +import java.util.Collection; +import org.opendatadiscovery.oddplatform.auth.AuthIdentityProvider; +import org.opendatadiscovery.oddplatform.dto.QueryExampleDto; +import org.opendatadiscovery.oddplatform.dto.policy.PolicyDto; +import org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto; +import org.opendatadiscovery.oddplatform.dto.policy.PolicyTypeDto; +import org.opendatadiscovery.oddplatform.dto.policy.QueryExamplePolicyResolverContext; +import org.opendatadiscovery.oddplatform.mapper.PolicyMapper; +import org.opendatadiscovery.oddplatform.model.tables.pojos.OwnerPojo; +import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveDataEntityQueryExampleRelationRepository; +import org.opendatadiscovery.oddplatform.service.PolicyService; +import org.opendatadiscovery.oddplatform.service.policy.PolicyPermissionExtractor; +import org.springframework.stereotype.Component; +import reactor.core.publisher.Mono; + +@Component +public class QueryExamplePermissionExtractor + extends AbstractContextualPermissionExtractor { + + private final AuthIdentityProvider authIdentityProvider; + private final ReactiveDataEntityQueryExampleRelationRepository repository; + private final PolicyPermissionExtractor permissionExtractor; + + public QueryExamplePermissionExtractor(final PolicyService policyService, + final PolicyMapper policyMapper, + final AuthIdentityProvider authIdentityProvider, + final ReactiveDataEntityQueryExampleRelationRepository repository, + final PolicyPermissionExtractor permissionExtractor) { + super(policyService, policyMapper); + this.authIdentityProvider = authIdentityProvider; + this.repository = repository; + this.permissionExtractor = permissionExtractor; + } + + @Override + protected Mono getContext(final long resourceId) { + final Mono dtoMono = repository.getQueryExampleDatasetRelations(resourceId); + + final Mono ownerPojoMono = authIdentityProvider.fetchAssociatedOwner(); + return ownerPojoMono + .zipWith(dtoMono) + .map(tuple + -> new QueryExamplePolicyResolverContext(tuple.getT2(), tuple.getT1())) + .switchIfEmpty(Mono.defer(() + -> dtoMono.map(dto -> new QueryExamplePolicyResolverContext(dto, null)))); + } + + @Override + protected Collection getPermissions(final PolicyDto policyDto, + final QueryExamplePolicyResolverContext context) { + return permissionExtractor.extractQueryExamplePermissions(policyDto.getStatements(), context); + } + + @Override + public PolicyTypeDto getResourceType() { + return PolicyTypeDto.QUERY_EXAMPLE; + } +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/PolicyPermissionExtractor.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/PolicyPermissionExtractor.java index 8c94afa51..bcbab28c1 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/PolicyPermissionExtractor.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/PolicyPermissionExtractor.java @@ -8,10 +8,12 @@ import org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto; import org.opendatadiscovery.oddplatform.dto.policy.PolicyStatementDto; import org.opendatadiscovery.oddplatform.dto.policy.PolicyTypeDto; +import org.opendatadiscovery.oddplatform.dto.policy.QueryExamplePolicyResolverContext; import org.opendatadiscovery.oddplatform.dto.policy.TermPolicyResolverContext; import org.opendatadiscovery.oddplatform.service.policy.resolver.ConditionResolver; import org.opendatadiscovery.oddplatform.service.policy.resolver.DataEntityConditionResolver; import org.opendatadiscovery.oddplatform.service.policy.resolver.NoContextConditionResolver; +import org.opendatadiscovery.oddplatform.service.policy.resolver.QueryExampleConditionResolver; import org.opendatadiscovery.oddplatform.service.policy.resolver.TermConditionResolver; import org.springframework.stereotype.Component; @@ -20,6 +22,7 @@ public class PolicyPermissionExtractor { private final DataEntityConditionResolver dataEntityResolver; private final TermConditionResolver termResolver; + private final QueryExampleConditionResolver queryExampleResolver; private final NoContextConditionResolver noContextConditionResolver; public Collection extractDataEntityPermissions(final List statements, @@ -32,6 +35,12 @@ public Collection extractTermPermissions(final List + extractQueryExamplePermissions(final List statements, + final QueryExamplePolicyResolverContext context) { + return permissions(statements, queryExampleResolver, PolicyTypeDto.QUERY_EXAMPLE, context); + } + public Collection extractManagementPermissions(final List statements) { return permissions(statements, noContextConditionResolver, PolicyTypeDto.MANAGEMENT, null); } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/comparer/queryexample/QueryExampleComparorFactory.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/comparer/queryexample/QueryExampleComparorFactory.java new file mode 100644 index 000000000..4fdb0afa2 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/comparer/queryexample/QueryExampleComparorFactory.java @@ -0,0 +1,15 @@ +package org.opendatadiscovery.oddplatform.service.policy.comparer.queryexample; + +import java.util.function.Function; +import org.opendatadiscovery.oddplatform.dto.QueryExampleDto; +import org.opendatadiscovery.oddplatform.dto.policy.QueryExamplePolicyResolverContext; +import org.opendatadiscovery.oddplatform.service.policy.comparer.Comparer; + +public final class QueryExampleComparorFactory { + private QueryExampleComparorFactory() {} + + public static Comparer + queryExample(final Function fieldExtractor) { + return new QueryExampleFieldComparer(fieldExtractor); + } +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/comparer/queryexample/QueryExampleFieldComparer.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/comparer/queryexample/QueryExampleFieldComparer.java new file mode 100644 index 000000000..ff79911d9 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/comparer/queryexample/QueryExampleFieldComparer.java @@ -0,0 +1,16 @@ +package org.opendatadiscovery.oddplatform.service.policy.comparer.queryexample; + +import java.util.function.Function; +import org.opendatadiscovery.oddplatform.dto.QueryExampleDto; +import org.opendatadiscovery.oddplatform.dto.policy.QueryExamplePolicyResolverContext; +import org.opendatadiscovery.oddplatform.service.policy.comparer.SimpleFieldComparer; + +public class QueryExampleFieldComparer extends SimpleFieldComparer { + public QueryExampleFieldComparer(final Function fieldExtractor) { + super(context -> fieldExtractor.apply(getQueryExample(context))); + } + + private static QueryExampleDto getQueryExample(final QueryExamplePolicyResolverContext context) { + return context.detailsDto(); + } +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/resolver/QueryExampleConditionResolver.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/resolver/QueryExampleConditionResolver.java new file mode 100644 index 000000000..fc025ed9d --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/service/policy/resolver/QueryExampleConditionResolver.java @@ -0,0 +1,24 @@ +package org.opendatadiscovery.oddplatform.service.policy.resolver; + +import java.util.Map; +import org.opendatadiscovery.oddplatform.dto.policy.PolicyConditionKeyDto; +import org.opendatadiscovery.oddplatform.dto.policy.QueryExamplePolicyResolverContext; +import org.opendatadiscovery.oddplatform.service.policy.comparer.Comparer; +import org.springframework.stereotype.Component; + +import static java.util.Map.entry; +import static org.opendatadiscovery.oddplatform.dto.policy.PolicyConditionKeyDto.QUERY_EXAMPLE_NAME; +import static org.opendatadiscovery.oddplatform.service.policy.comparer.queryexample.QueryExampleComparorFactory.queryExample; + +@Component +public class QueryExampleConditionResolver extends AbstractConditionResolver { + + private static final Map> FIELDS = Map.ofEntries( + entry(QUERY_EXAMPLE_NAME, queryExample(item -> item.queryExamplePojo().getDefinition())) + ); + + @Override + protected Map> getFieldExtractorMap() { + return FIELDS; + } +} diff --git a/odd-platform-api/src/main/resources/db/migration/V0_0_88__add_query_example_policy.sql b/odd-platform-api/src/main/resources/db/migration/V0_0_88__add_query_example_policy.sql new file mode 100644 index 000000000..fb273d7c4 --- /dev/null +++ b/odd-platform-api/src/main/resources/db/migration/V0_0_88__add_query_example_policy.sql @@ -0,0 +1,11 @@ +UPDATE policy +SET policy = + jsonb_pretty( + regexp_replace( + policy, + '"statements": \[', + '"statements": [{' || '"resource": {"type": "QUERY_EXAMPLE"}, "permissions": ["ALL"]},', + 'g' + )::jsonb + ) +WHERE name = 'Administrator'; \ No newline at end of file diff --git a/odd-platform-api/src/main/resources/schema/policy_schema.json b/odd-platform-api/src/main/resources/schema/policy_schema.json index 778eb5704..6ff2eeb3c 100644 --- a/odd-platform-api/src/main/resources/schema/policy_schema.json +++ b/odd-platform-api/src/main/resources/schema/policy_schema.json @@ -34,6 +34,9 @@ { "$ref": "#/$defs/data_entity_permission" }, + { + "$ref": "#/$defs/query_example_permissions" + }, { "$ref": "#/$defs/management_permissions" }, @@ -123,6 +126,42 @@ } } } + },{ + "if": { + "type": "object", + "properties": { + "resource": { + "type": "object", + "properties": { + "type": { + "const": "QUERY_EXAMPLE" + } + }, + "required": [ + "type" + ] + } + }, + "required": [ + "resource" + ] + }, + "then": { + "properties": { + "permissions": { + "items": { + "anyOf": [ + { + "$ref": "#/$defs/query_example_permissions" + }, + { + "$ref": "#/$defs/all_permissions" + } + ] + } + } + } + } }, { "if": { @@ -177,6 +216,9 @@ }, { "$ref": "#/$defs/data_entity_policy_resource_condition" + }, + { + "$ref": "#/$defs/query_example_policy_resource_condition" } ] } @@ -399,6 +441,76 @@ "maxProperties": 1, "minProperties": 1 }, + "query_example_policy_resource_condition": { + "type": "object", + "additionalProperties": false, + "properties": { + "all": { + "type": "array", + "items": { + "$ref": "#/$defs/query_example_policy_resource_condition" + } + }, + "any": { + "type": "array", + "items": { + "$ref": "#/$defs/query_example_policy_resource_condition" + } + }, + "eq": { + "$ref": "#/$defs/query_example_policy_resource_condition_unary" + }, + "not_eq": { + "$ref": "#/$defs/query_example_policy_resource_condition_unary" + }, + "match": { + "$ref": "#/$defs/query_example_policy_resource_condition_unary" + }, + "not_match": { + "$ref": "#/$defs/query_example_policy_resource_condition_unary" + } + }, + "anyOf": [ + { + "required": [ + "all" + ] + }, + { + "required": [ + "any" + ] + }, + { + "required": [ + "eq" + ] + }, + { + "required": [ + "not_eq" + ] + }, + { + "required": [ + "match" + ] + }, + { + "required": [ + "not_match" + ] + } + ] + }, + "query_example_policy_resource_condition_unary": { + "type": "object", + "propertyNames": { + "$ref": "#/$defs/query_example_condition_key" + }, + "maxProperties": 1, + "minProperties": 1 + }, "data_entity_resource_condition_key": { "type": "string", "enum": [ @@ -425,6 +537,12 @@ "term:owner:title" ] }, + "query_example_condition_key": { + "type": "string", + "enum": [ + "queryexample:name" + ] + }, "data_entity_permission": { "type": "string", "enum": [ @@ -452,7 +570,9 @@ "DATASET_FIELD_DELETE_TERM", "DATA_ENTITY_GROUP_UPDATE", "DATA_ENTITY_ATTACHMENT_MANAGE", - "DATA_ENTITY_STATUS_UPDATE" + "DATA_ENTITY_STATUS_UPDATE", + "QUERY_EXAMPLE_DATASET_DELETE", + "QUERY_EXAMPLE_DATASET_CREATE" ] }, "term_permissions": { @@ -466,6 +586,13 @@ "TERM_OWNERSHIP_DELETE" ] }, + "query_example_permissions": { + "type": "string", + "enum": [ + "QUERY_EXAMPLE_UPDATE", + "QUERY_EXAMPLE_DELETE" + ] + }, "management_permissions": { "type": "string", "enum": [ @@ -509,6 +636,7 @@ "enum": [ "DATA_ENTITY", "TERM", + "QUERY_EXAMPLE", "MANAGEMENT" ] } diff --git a/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/service/QueryExampleServiceTest.java b/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/service/QueryExampleServiceTest.java index 208e80186..b922138cf 100644 --- a/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/service/QueryExampleServiceTest.java +++ b/odd-platform-api/src/test/java/org/opendatadiscovery/oddplatform/service/QueryExampleServiceTest.java @@ -12,11 +12,11 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityList; +import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityQueryExampleFormData; import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityRef; import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityStatus; import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityStatusEnum; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExample; -import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleDatasetFormData; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleDetails; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleFormData; import org.opendatadiscovery.oddplatform.dto.DataEntityDimensionsDto; @@ -149,8 +149,8 @@ public void createQueryExampleTest(final QueryExampleFormData formData, @ParameterizedTest @MethodSource("queryExampleRelationProvider") @DisplayName("Creates new queryExample Relations") - public void createQueryExampleRelationsTest(final Long queryExampleId, - final QueryExampleDatasetFormData formData, + public void createQueryExampleRelationsTest(final Long datasetId, + final DataEntityQueryExampleFormData formData, final DataEntityToQueryExamplePojo entityToQueryExamplePojo, final QueryExampleDto queryExampleDto, final QueryExample expected) { @@ -162,7 +162,7 @@ public void createQueryExampleRelationsTest(final Long queryExampleId, .updateQueryExampleVectorsForDataEntity(anyLong())).thenReturn(Mono.just(1)); queryExampleService - .createQueryExampleToDatasetRelationship(queryExampleId, formData) + .createQueryExampleToDatasetRelationship(datasetId, formData.getQueryExampleId()) .as(StepVerifier::create) .assertNext(item -> { assertEquals(expected.getDefinition(), item.getDefinition()); @@ -245,8 +245,7 @@ public void createDuplicateRelations() { .thenReturn(Mono.empty()); queryExampleService - .createQueryExampleToDatasetRelationship(1L, - new QueryExampleDatasetFormData().datasetId(1L)) + .createQueryExampleToDatasetRelationship(1L, 1L) .as(StepVerifier::create) .verifyError(BadUserRequestException.class); } @@ -293,7 +292,7 @@ private static Stream queryExampleProvider() { private static Stream queryExampleRelationProvider() { return Stream.of( Arguments.arguments(1L, - new QueryExampleDatasetFormData().datasetId(1L), + new DataEntityQueryExampleFormData().queryExampleId(1L), new DataEntityToQueryExamplePojo() .setDataEntityId(1L) .setQueryExampleId(1L), diff --git a/odd-platform-specification/components.yaml b/odd-platform-specification/components.yaml index 1afac10c0..e799ed936 100644 --- a/odd-platform-specification/components.yaml +++ b/odd-platform-specification/components.yaml @@ -2744,14 +2744,14 @@ components: - search_id - query - QueryExampleDatasetFormData: + DataEntityQueryExampleFormData: type: object properties: - dataset_id: + query_example_id: type: integer format: int64 required: - - dataset_id + - query_example_id ActivityCountInfo: type: object @@ -3260,6 +3260,7 @@ components: enum: - DATA_ENTITY - TERM + - QUERY_EXAMPLE - MANAGEMENT MessageChannel: diff --git a/odd-platform-specification/openapi.yaml b/odd-platform-specification/openapi.yaml index 83ad19fdf..db87c62fd 100644 --- a/odd-platform-specification/openapi.yaml +++ b/odd-platform-specification/openapi.yaml @@ -992,6 +992,29 @@ paths: tags: - dataEntity + /api/dataentities/{data_entity_id}/queryexample: + post: + summary: add Dataset to QueryExample + description: Creates a new relation between QueryExample and Dataset + operationId: createQueryExampleToDatasetRelationshipNew + parameters: + - $ref: './components.yaml/#/components/parameters/DataEntityIdParam' + requestBody: + required: true + content: + application/json: + schema: + $ref: './components.yaml/#/components/schemas/DataEntityQueryExampleFormData' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: './components.yaml/#/components/schemas/QueryExample' + tags: + - dataEntity + /api/dataentities/{data_entity_id}/statuses: put: summary: Update status to data entity @@ -1029,6 +1052,20 @@ paths: tags: - dataEntity + /api/dataentities/{data_entity_id}/queryexample/{example_id}: + delete: + summary: Delete Dataset from QueryExample + description: Deletes relationship between QueryExample and Dataset + operationId: deleteQueryExampleToDatasetRelationshipNew + parameters: + - $ref: './components.yaml/#/components/parameters/DataEntityIdParam' + - $ref: './components.yaml/#/components/parameters/QueryExampleIdParam' + responses: + '204': + $ref: './components.yaml/#/components/responses/Deleted' + tags: + - dataEntity + /api/dataentities/{data_entity_id}/data_entity_group: post: summary: Add data entity to group @@ -2245,43 +2282,6 @@ paths: tags: - queryExample - /api/queryexample/{example_id}/dataset: - post: - summary: add Dataset to QueryExample - description: Creates a new relation between QueryExample and Dataset - operationId: createQueryExampleToDatasetRelationship - parameters: - - $ref: './components.yaml/#/components/parameters/QueryExampleIdParam' - requestBody: - required: true - content: - application/json: - schema: - $ref: './components.yaml/#/components/schemas/QueryExampleDatasetFormData' - responses: - '200': - description: OK - content: - application/json: - schema: - $ref: './components.yaml/#/components/schemas/QueryExample' - tags: - - queryExample - - /api/queryexample/{example_id}/dataset/{data_entity_id}: - delete: - summary: Delete Dataset from QueryExample - description: Deletes relationship between QueryExample and Dataset - operationId: deleteQueryExampleToDatasetRelationship - parameters: - - $ref: './components.yaml/#/components/parameters/QueryExampleIdParam' - - $ref: './components.yaml/#/components/parameters/DataEntityIdParam' - responses: - '204': - $ref: './components.yaml/#/components/responses/Deleted' - tags: - - queryExample - /api/queryexample/search/suggestions: get: summary: Get QueryExample search suggestions by query diff --git a/odd-platform-ui/src/components/DataEntityDetails/DataEntityDetailsRoutes/DataEntityDetailsRoutes.tsx b/odd-platform-ui/src/components/DataEntityDetails/DataEntityDetailsRoutes/DataEntityDetailsRoutes.tsx index 96b80bb92..4c3472528 100644 --- a/odd-platform-ui/src/components/DataEntityDetails/DataEntityDetailsRoutes/DataEntityDetailsRoutes.tsx +++ b/odd-platform-ui/src/components/DataEntityDetails/DataEntityDetailsRoutes/DataEntityDetailsRoutes.tsx @@ -120,7 +120,7 @@ const DataEntityDetailsRoutes = () => { Permission.QUERY_EXAMPLE_DATASET_CREATE, Permission.QUERY_EXAMPLE_DATASET_DELETE, ]} - resourcePermissions={[]} + resourcePermissions={resourcePermissions} Component={DataEntityDetailsQueryExamples} /> } diff --git a/odd-platform-ui/src/components/DataEntityDetails/DataEntityQueryExamples/AssignEntityQueryExampleForm.tsx b/odd-platform-ui/src/components/DataEntityDetails/DataEntityQueryExamples/AssignEntityQueryExampleForm.tsx index f355636cc..4c2a6d73c 100644 --- a/odd-platform-ui/src/components/DataEntityDetails/DataEntityQueryExamples/AssignEntityQueryExampleForm.tsx +++ b/odd-platform-ui/src/components/DataEntityDetails/DataEntityQueryExamples/AssignEntityQueryExampleForm.tsx @@ -35,8 +35,8 @@ const AssignEntityQueryExampleForm: React.FC = ({ const onSubmit = ({ exampleId }: FormData) => { mutateAsync({ - exampleId, - queryExampleDatasetFormData: { datasetId: dataEntityId }, + dataEntityId, + dataEntityQueryExampleFormData: { queryExampleId: exampleId }, }).then(_ => { reset(); }); diff --git a/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsContainer.tsx b/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsContainer.tsx index 92316817a..59c72dbcd 100644 --- a/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsContainer.tsx +++ b/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsContainer.tsx @@ -1,34 +1,29 @@ import React, { useCallback, useMemo, useState } from 'react'; import { Box, Grid, Typography } from '@mui/material'; -import { - useDeleteQueryExample, - useGetQueryExampleDetails, -} from 'lib/hooks/api/dataModelling/queryExamples'; +import { useGetQueryExampleDetails } from 'lib/hooks/api/dataModelling/queryExamples'; import { useAppDateTime } from 'lib/hooks'; -import { - AppLoadingPage, - AppMenuItem, - AppPopover, - Button, - ConfirmationDialog, -} from 'components/shared/elements'; -import { EditIcon, KebabIcon, TimeGapIcon } from 'components/shared/icons'; -import { useTranslation } from 'react-i18next'; -import { useNavigate } from 'react-router-dom'; -import { queryExamplesPath, useQueryExamplesRouteParams } from 'routes'; +import { AppLoadingPage } from 'components/shared/elements'; +import { TimeGapIcon } from 'components/shared/icons'; +import { useQueryExamplesRouteParams } from 'routes'; +import { WithPermissionsProvider } from 'components/shared/contexts'; +import { Permission, PermissionResourceType } from 'generated-sources'; +import { useResourcePermissions } from 'lib/hooks/api/permissions'; import QueryExampleDetailsTabs from './QueryExampleDetailsTabs'; import QueryExampleDetailsOverview from './QueryExampleDetailsOverview'; import QueryExampleDetailsLinkedEntities from './QueryExampleDetailsLinkedEntities'; -import QueryExampleForm from '../QueryExampleForm/QueryExampleForm'; +import { QueryExampleDetailsContainerActions } from './QueryExampleDetailsContainerActions'; const QueryExampleDetailsContainer: React.FC = () => { const { queryExampleId: exampleId } = useQueryExamplesRouteParams(); - const { t } = useTranslation(); - const navigate = useNavigate(); + + const { data: resourcePermissions } = useResourcePermissions({ + resourceId: exampleId, + permissionResourceType: PermissionResourceType.QUERY_EXAMPLE, + }); + const { data: queryExampleDetails, isLoading } = useGetQueryExampleDetails({ exampleId, }); - const { mutateAsync: deleteQueryExample } = useDeleteQueryExample(); const [selectedTab, setSelectedTab] = useState(0); const handleTabChange = useCallback(() => { @@ -46,14 +41,6 @@ const QueryExampleDetailsContainer: React.FC = () => { [queryExampleDetails?.updatedAt, formatDistanceToNowStrict] ); - const handleDelete = useCallback( - async (id: number) => { - await deleteQueryExample({ exampleId: id }); - navigate(queryExamplesPath()); - }, - [queryExampleDetails?.id] - ); - return queryExampleDetails && !isLoading ? ( @@ -63,43 +50,18 @@ const QueryExampleDetailsContainer: React.FC = () => { {updatedAt} - } - sx={{ ml: 1 }} - /> - } - queryExampleDetails={queryExampleDetails} - /> - ( -