From ab1bbb362c78753c2a64f2114e263cb9cddade0a Mon Sep 17 00:00:00 2001 From: Vladysl <45620393+Vladysl@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:42:29 +0300 Subject: [PATCH] 1660 - Link Query Examples to Terms (#1666) --- .../auth/util/SecurityConstants.java | 9 + .../controller/QueryExampleController.java | 7 + .../controller/TermController.java | 22 +++ .../oddplatform/dto/QueryExampleDto.java | 4 +- .../dto/policy/PolicyPermissionDto.java | 2 + .../oddplatform/dto/term/TermDetailsDto.java | 2 +- .../oddplatform/dto/term/TermDto.java | 1 + .../mapper/QueryExampleMapper.java | 20 ++- .../oddplatform/mapper/TermMapper.java | 8 + ...ityQueryExampleRelationRepositoryImpl.java | 65 ++++++- .../ReactiveQueryExampleRepositoryImpl.java | 53 +++++- ...iveTermQueryExampleRelationRepository.java | 18 ++ ...ermQueryExampleRelationRepositoryImpl.java | 163 ++++++++++++++++++ .../reactive/ReactiveTermRepositoryImpl.java | 8 + .../service/QueryExampleService.java | 7 + .../service/QueryExampleServiceImpl.java | 44 ++++- ..._added_query_example_to_term_relations.sql | 12 ++ .../service/QueryExampleServiceTest.java | 13 +- odd-platform-specification/components.yaml | 27 +++ odd-platform-specification/openapi.yaml | 55 ++++++ .../DataEntityDetailsQueryExamples.tsx | 2 +- .../QuertExampleDetailsLinkedTermsItem.tsx | 49 ++++++ .../QueryExampleDetailsContainer.tsx | 26 ++- .../QueryExampleDetailsLinkedTerms.tsx | 40 +++++ .../QueryExampleDetailsTabs.tsx | 52 ++++-- .../TermDetailsRoutes/TermDetailsRoutes.tsx | 46 +++-- .../TermDetailsTabs/TermDetailsTabs.tsx | 4 + .../AssignTermQueryExampleForm.tsx | 87 ++++++++++ .../TermQueryExamples/TermQueryExamples.tsx | 63 +++++++ .../QueryExamples/QueryExamplesListHeader.tsx | 7 +- .../QueryExamples/QueryExamplesListItem.tsx | 110 +++++++----- .../QueryExamplesTermUnlinkButton.tsx | 43 +++++ .../hooks/api/dataModelling/queryExamples.ts | 11 ++ odd-platform-ui/src/lib/hooks/api/terms.ts | 52 +++++- odd-platform-ui/src/routes/termsRoutes.ts | 1 + 35 files changed, 1036 insertions(+), 97 deletions(-) create mode 100644 odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveTermQueryExampleRelationRepository.java create mode 100644 odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveTermQueryExampleRelationRepositoryImpl.java create mode 100644 odd-platform-api/src/main/resources/db/migration/V0_0_90__added_query_example_to_term_relations.sql create mode 100644 odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QuertExampleDetailsLinkedTermsItem.tsx create mode 100644 odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsLinkedTerms.tsx create mode 100644 odd-platform-ui/src/components/Terms/TermDetails/TermQueryExamples/AssignTermQueryExampleForm.tsx create mode 100644 odd-platform-ui/src/components/Terms/TermDetails/TermQueryExamples/TermQueryExamples.tsx create mode 100644 odd-platform-ui/src/components/shared/elements/QueryExamples/QueryExamplesTermUnlinkButton.tsx 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 b2c430509..9782189e9 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 @@ -68,6 +68,8 @@ import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.QUERY_EXAMPLE_DATASET_CREATE; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.QUERY_EXAMPLE_DATASET_DELETE; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.QUERY_EXAMPLE_DELETE; +import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.QUERY_EXAMPLE_TERM_CREATE; +import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.QUERY_EXAMPLE_TERM_DELETE; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.QUERY_EXAMPLE_UPDATE; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.ROLE_CREATE; import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.ROLE_DELETE; @@ -182,6 +184,13 @@ NO_CONTEXT, new PathPatternParserServerWebExchangeMatcher( TERM_OWNERSHIP_DELETE), new SecurityRule(TERM, new PathPatternParserServerWebExchangeMatcher("/api/terms/{term_id}/tags", PUT), TERM_TAGS_UPDATE), + new SecurityRule(TERM, + new PathPatternParserServerWebExchangeMatcher("/api/terms/{term_id}/queryexample", POST), + QUERY_EXAMPLE_TERM_CREATE), + new SecurityRule(TERM, + new PathPatternParserServerWebExchangeMatcher("/api/terms/{term_id}/queryexample/{example_id}", + DELETE), + QUERY_EXAMPLE_TERM_DELETE), new SecurityRule( DATA_ENTITY, new PathPatternParserServerWebExchangeMatcher("/api/dataentities/{data_entity_id}/description", PUT), 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 a6e2ff57d..0abff257e 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 @@ -55,6 +55,13 @@ public Mono> getQueryExampleByDatasetId(final L .map(ResponseEntity::ok); } + @Override + public Mono> getQueryExampleByTermId(final Long termId, + final ServerWebExchange exchange) { + return queryExampleService.getQueryExampleByTermId(termId) + .map(ResponseEntity::ok); + } + @Override public Mono> getQueryExampleDetails(final Long exampleId, final ServerWebExchange exchange) { diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/TermController.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/TermController.java index 2a0b7eba0..c8ee8d1d2 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/TermController.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/controller/TermController.java @@ -10,6 +10,8 @@ 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.QueryExampleTermFormData; import org.opendatadiscovery.oddplatform.api.contract.model.Tag; import org.opendatadiscovery.oddplatform.api.contract.model.TagsFormData; import org.opendatadiscovery.oddplatform.api.contract.model.TermDetails; @@ -21,6 +23,7 @@ import org.opendatadiscovery.oddplatform.api.contract.model.TermSearchFormData; import org.opendatadiscovery.oddplatform.service.DataEntityService; import org.opendatadiscovery.oddplatform.service.DatasetFieldService; +import org.opendatadiscovery.oddplatform.service.QueryExampleService; import org.opendatadiscovery.oddplatform.service.term.TermOwnershipService; import org.opendatadiscovery.oddplatform.service.term.TermSearchService; import org.opendatadiscovery.oddplatform.service.term.TermService; @@ -39,6 +42,7 @@ public class TermController implements TermApi { private final DatasetFieldService datasetFieldService; private final TermSearchService termSearchService; private final TermOwnershipService termOwnershipService; + private final QueryExampleService queryExampleService; @Override public Mono> getTermsList(final Integer page, final Integer size, @@ -195,4 +199,22 @@ public Mono> updateTermSearchFacets( .flatMap(fd -> termSearchService.updateFacets(searchId, fd)) .map(ResponseEntity::ok); } + + @Override + public Mono> + createQueryExampleToTermRelationship(final Long termId, + final Mono queryExampleTermFormData, + final ServerWebExchange exchange) { + return queryExampleTermFormData + .flatMap(item -> queryExampleService.linkTermWithQueryExample(termId, item)) + .map(ResponseEntity::ok); + } + + @Override + public Mono> deleteQueryExampleToTermRelationship(final Long termId, + final Long exampleId, + final ServerWebExchange exchange) { + return queryExampleService.removeTermFromQueryExample(termId, exampleId) + .thenReturn(ResponseEntity.noContent().build()); + } } 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 1c2f8f12e..eeb4596b6 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 @@ -1,9 +1,11 @@ package org.opendatadiscovery.oddplatform.dto; import java.util.List; +import org.opendatadiscovery.oddplatform.dto.term.LinkedTermDto; import org.opendatadiscovery.oddplatform.model.tables.pojos.DataEntityPojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.QueryExamplePojo; public record QueryExampleDto(QueryExamplePojo queryExamplePojo, - List linkedEntities) { + List linkedEntities, + List linkedTerms) { } 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 95736c990..a24a3a9d3 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 @@ -46,6 +46,8 @@ public enum PolicyPermissionDto { TERM_OWNERSHIP_UPDATE(TERM), TERM_OWNERSHIP_DELETE(TERM), TERM_TAGS_UPDATE(TERM), + QUERY_EXAMPLE_TERM_CREATE(TERM), + QUERY_EXAMPLE_TERM_DELETE(TERM), DATA_SOURCE_CREATE(MANAGEMENT), DATA_SOURCE_UPDATE(MANAGEMENT), DATA_SOURCE_DELETE(MANAGEMENT), diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/term/TermDetailsDto.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/term/TermDetailsDto.java index dfe961ef0..515c7a2c4 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/term/TermDetailsDto.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/term/TermDetailsDto.java @@ -15,6 +15,6 @@ public class TermDetailsDto { public TermDetailsDto(final TermRefDto termRefDto) { this.tags = null; - this.termDto = new TermDto(termRefDto, null, null, null); + this.termDto = new TermDto(termRefDto, null, null, null, null); } } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/term/TermDto.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/term/TermDto.java index 87ca10528..4c0253edc 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/term/TermDto.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/dto/term/TermDto.java @@ -10,5 +10,6 @@ public class TermDto { private final TermRefDto termRefDto; private final Integer entitiesUsingCount; private final Integer columnsUsingCount; + private final Integer queryExampleUsingCount; private final Set ownerships; } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/QueryExampleMapper.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/QueryExampleMapper.java index 5da104f16..728076acd 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/QueryExampleMapper.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/QueryExampleMapper.java @@ -14,6 +14,7 @@ import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleRefList; import org.opendatadiscovery.oddplatform.dto.DataEntityDimensionsDto; import org.opendatadiscovery.oddplatform.dto.QueryExampleDto; +import org.opendatadiscovery.oddplatform.dto.term.LinkedTermDto; import org.opendatadiscovery.oddplatform.model.tables.pojos.DataEntityPojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.QueryExamplePojo; import org.opendatadiscovery.oddplatform.utils.Page; @@ -27,6 +28,7 @@ public abstract class QueryExampleMapper { protected DataEntityMapper dataEntityMapper; protected DateTimeMapper dateTimeMapper; + protected TermMapper termMapper; @Autowired public void setDateTimeMapper(final DateTimeMapper dateTimeMapper) { @@ -38,6 +40,11 @@ public void setDataEntityMapper(final DataEntityMapper dataEntityMapper) { this.dataEntityMapper = dataEntityMapper; } + @Autowired + public void setTermMapper(final TermMapper termMapper) { + this.termMapper = termMapper; + } + public abstract QueryExamplePojo mapToPojo(final QueryExampleFormData dto); public abstract QueryExamplePojo applyToPojo(final QueryExampleFormData dto, @@ -72,17 +79,18 @@ public List mapToQueryExampleList( return queryExampleDtos .stream() .map(item -> mapToQueryExample(item.queryExamplePojo(), - item.linkedEntities())) + item.linkedEntities(), item.linkedTerms())) .collect(Collectors.toList()); } public QueryExample mapToQueryExample( - final QueryExamplePojo pojo, final List dataEntities) { + final QueryExamplePojo pojo, final List dataEntities, final List linkedTerms) { return new QueryExample() .id(pojo.getId()) .definition(pojo.getDefinition()) .query(pojo.getQuery()) - .linkedEntities(mapDataEntityRefList(dataEntities)); + .linkedEntities(mapDataEntityRefList(dataEntities)) + .linkedTerms(termMapper.mapToLinkedTermList(linkedTerms)); } public List mapDataEntityRefList(final List dataEntities) { @@ -92,13 +100,15 @@ public List mapDataEntityRefList(final List dataE } public QueryExampleDetails mapToQueryExampleDetails( - final QueryExamplePojo pojo, final List dataEntities) { + final QueryExamplePojo pojo, final List dataEntities, + final List terms) { return new QueryExampleDetails() .id(pojo.getId()) .definition(pojo.getDefinition()) .query(pojo.getQuery()) .createdAt(dateTimeMapper.mapUTCDateTime(pojo.getCreatedAt())) .updatedAt(dateTimeMapper.mapUTCDateTime(pojo.getUpdatedAt())) - .linkedEntities(dataEntityMapper.mapPojos(dataEntities)); + .linkedEntities(dataEntityMapper.mapPojos(dataEntities)) + .linkedTerms(termMapper.mapToLinkedTermList(terms)); } } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/TermMapper.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/TermMapper.java index 84b5b9517..4d3c9faa7 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/TermMapper.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/mapper/TermMapper.java @@ -6,6 +6,7 @@ import org.mapstruct.Mapping; import org.mapstruct.MappingTarget; import org.opendatadiscovery.oddplatform.api.contract.model.LinkedTerm; +import org.opendatadiscovery.oddplatform.api.contract.model.LinkedTermList; import org.opendatadiscovery.oddplatform.api.contract.model.PageInfo; import org.opendatadiscovery.oddplatform.api.contract.model.Term; import org.opendatadiscovery.oddplatform.api.contract.model.TermDetails; @@ -55,6 +56,12 @@ default TermRefList mapToRefPage(final Page page) { List mapToList(final List dtos); + List mapListToLinkedTermList(final List dtos); + + default LinkedTermList mapToLinkedTermList(final List dtos) { + return new LinkedTermList().items(mapListToLinkedTermList(dtos)); + } + default TermList mapToPage(final Page page) { return new TermList() .items(mapToList(page.getData())) @@ -68,6 +75,7 @@ default TermList mapToPage(final Page page) { @Mapping(source = "dto.termDto.ownerships", target = "ownership") @Mapping(source = "dto.termDto.entitiesUsingCount", target = "entitiesUsingCount") @Mapping(source = "dto.termDto.columnsUsingCount", target = "columnsUsingCount") + @Mapping(source = "dto.termDto.queryExampleUsingCount", target = "queryExampleUsingCount") TermDetails mapToDetails(final TermDetailsDto dto); @Mapping(source = "dto.termRefDto.term", target = ".") diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveDataEntityQueryExampleRelationRepositoryImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveDataEntityQueryExampleRelationRepositoryImpl.java index c8c5c6887..ba248bcb5 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveDataEntityQueryExampleRelationRepositoryImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveDataEntityQueryExampleRelationRepositoryImpl.java @@ -2,6 +2,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import org.jooq.Record; import org.jooq.Record1; import org.jooq.SelectConditionStep; @@ -9,9 +12,14 @@ import org.jooq.Table; import org.jooq.impl.DSL; import org.opendatadiscovery.oddplatform.dto.QueryExampleDto; +import org.opendatadiscovery.oddplatform.dto.term.LinkedTermDto; +import org.opendatadiscovery.oddplatform.dto.term.TermRefDto; import org.opendatadiscovery.oddplatform.model.tables.pojos.DataEntityPojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.DataEntityToQueryExamplePojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.NamespacePojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.QueryExamplePojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.QueryExampleToTermPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.TermPojo; import org.opendatadiscovery.oddplatform.model.tables.records.DataEntityToQueryExampleRecord; import org.opendatadiscovery.oddplatform.repository.util.JooqQueryHelper; import org.opendatadiscovery.oddplatform.repository.util.JooqReactiveOperations; @@ -20,11 +28,15 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import static java.util.function.Function.identity; import static org.jooq.impl.DSL.field; import static org.jooq.impl.DSL.jsonArrayAgg; import static org.opendatadiscovery.oddplatform.model.Tables.DATA_ENTITY; import static org.opendatadiscovery.oddplatform.model.Tables.DATA_ENTITY_TO_QUERY_EXAMPLE; +import static org.opendatadiscovery.oddplatform.model.Tables.NAMESPACE; import static org.opendatadiscovery.oddplatform.model.Tables.QUERY_EXAMPLE; +import static org.opendatadiscovery.oddplatform.model.Tables.QUERY_EXAMPLE_TO_TERM; +import static org.opendatadiscovery.oddplatform.model.Tables.TERM; @Repository public class ReactiveDataEntityQueryExampleRelationRepositoryImpl @@ -32,6 +44,9 @@ public class ReactiveDataEntityQueryExampleRelationRepositoryImpl implements ReactiveDataEntityQueryExampleRelationRepository { private static final String AGG_DATA_ENTITIES_FIELD = "dataEntities"; private static final String QUERY_EXAMPLES_CTE = "query_examples_cte"; + public static final String TERMS = "terms"; + public static final String TERM_NAMESPACES = "term_namespaces"; + public static final String TERM_RELATIONS = "term_relations"; private final JooqRecordHelper jooqRecordHelper; @@ -60,17 +75,26 @@ public Mono createRelationWithDataEntity(final lon public Mono getQueryExampleDatasetRelations(final long queryExample) { final SelectHavingStep query = DSL.select(QUERY_EXAMPLE.asterisk()) .select(jsonArrayAgg(field(DATA_ENTITY.asterisk().toString())).as(AGG_DATA_ENTITIES_FIELD)) + .select(jsonArrayAgg(field(TERM.asterisk().toString())).as(TERMS)) + .select(jsonArrayAgg(field(NAMESPACE.asterisk().toString())).as(TERM_NAMESPACES)) + .select(jsonArrayAgg(field(QUERY_EXAMPLE_TO_TERM.asterisk().toString())).as(TERM_RELATIONS)) .from(QUERY_EXAMPLE) .leftJoin(DATA_ENTITY_TO_QUERY_EXAMPLE) .on(DATA_ENTITY_TO_QUERY_EXAMPLE.QUERY_EXAMPLE_ID.eq(QUERY_EXAMPLE.ID)) .leftJoin(DATA_ENTITY) .on(DATA_ENTITY.ID.eq(DATA_ENTITY_TO_QUERY_EXAMPLE.DATA_ENTITY_ID)) + .leftJoin(QUERY_EXAMPLE_TO_TERM) + .on(QUERY_EXAMPLE.ID.eq(QUERY_EXAMPLE_TO_TERM.QUERY_EXAMPLE_ID)) + .leftJoin(TERM) + .on(QUERY_EXAMPLE_TO_TERM.TERM_ID.eq(TERM.ID)).and(TERM.DELETED_AT.isNull()) + .leftJoin(NAMESPACE).on(TERM.NAMESPACE_ID.eq(NAMESPACE.ID)) .where(QUERY_EXAMPLE.ID.eq(queryExample)) .groupBy(QUERY_EXAMPLE.ID); return jooqReactiveOperations.mono(query) .map(r -> new QueryExampleDto(r.into(QUERY_EXAMPLE).into(QueryExamplePojo.class), - extractDataEntityPojos(r))); + extractDataEntityPojos(r), + extractTerms(r))); } @Override @@ -107,6 +131,9 @@ public Flux getQueryExampleDatasetRelationsByDataEntity(final L .as(queryExampleSelect) .select(QUERY_EXAMPLE.asterisk()) .select(jsonArrayAgg(field(DATA_ENTITY.asterisk().toString())).as(AGG_DATA_ENTITIES_FIELD)) + .select(jsonArrayAgg(field(TERM.asterisk().toString())).as(TERMS)) + .select(jsonArrayAgg(field(NAMESPACE.asterisk().toString())).as(TERM_NAMESPACES)) + .select(jsonArrayAgg(field(QUERY_EXAMPLE_TO_TERM.asterisk().toString())).as(TERM_RELATIONS)) .from(exampleCTE.getName()) .join(QUERY_EXAMPLE) .on(QUERY_EXAMPLE.ID.eq(exampleCTE.field(DATA_ENTITY_TO_QUERY_EXAMPLE.QUERY_EXAMPLE_ID))) @@ -114,15 +141,49 @@ public Flux getQueryExampleDatasetRelationsByDataEntity(final L .on(DATA_ENTITY_TO_QUERY_EXAMPLE.QUERY_EXAMPLE_ID.eq(QUERY_EXAMPLE.ID)) .leftJoin(DATA_ENTITY) .on(DATA_ENTITY.ID.eq(DATA_ENTITY_TO_QUERY_EXAMPLE.DATA_ENTITY_ID)) + .leftJoin(QUERY_EXAMPLE_TO_TERM) + .on(QUERY_EXAMPLE.ID.eq(QUERY_EXAMPLE_TO_TERM.QUERY_EXAMPLE_ID)) + .leftJoin(TERM) + .on(QUERY_EXAMPLE_TO_TERM.TERM_ID.eq(TERM.ID)).and(TERM.DELETED_AT.isNull()) + .leftJoin(NAMESPACE).on(TERM.NAMESPACE_ID.eq(NAMESPACE.ID)) .groupBy(QUERY_EXAMPLE.ID); return jooqReactiveOperations.flux(query) .map(r -> new QueryExampleDto(r.into(QUERY_EXAMPLE).into(QueryExamplePojo.class), - extractDataEntityPojos(r))); + extractDataEntityPojos(r), + extractTerms(r) + )); } private List extractDataEntityPojos(final Record r) { return new ArrayList<>(jooqRecordHelper.extractAggRelation(r, AGG_DATA_ENTITIES_FIELD, DataEntityPojo.class)); } + + private List extractTerms(final Record record) { + final Set terms = jooqRecordHelper.extractAggRelation(record, TERMS, TermPojo.class); + + final Map namespaces = jooqRecordHelper + .extractAggRelation(record, TERM_NAMESPACES, NamespacePojo.class) + .stream() + .collect(Collectors.toMap(NamespacePojo::getId, identity())); + + final Map> relations = jooqRecordHelper + .extractAggRelation(record, TERM_RELATIONS, QueryExampleToTermPojo.class) + .stream() + .collect(Collectors.groupingBy(QueryExampleToTermPojo::getTermId)); + + return terms.stream() + .map(pojo -> { + final TermRefDto termRefDto = TermRefDto.builder() + .term(pojo) + .namespace(namespaces.get(pojo.getNamespaceId())) + .build(); + final boolean isDescriptionLink = relations.getOrDefault(pojo.getId(), List.of()).stream() + .anyMatch(r -> Boolean.TRUE.equals(r.getIsDescriptionLink())); + + return new LinkedTermDto(termRefDto, isDescriptionLink); + }) + .toList(); + } } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveQueryExampleRepositoryImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveQueryExampleRepositoryImpl.java index 2c2f05762..c6e36ffb9 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveQueryExampleRepositoryImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveQueryExampleRepositoryImpl.java @@ -3,6 +3,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.jooq.Condition; import org.jooq.Field; @@ -15,8 +18,13 @@ import org.opendatadiscovery.oddplatform.dto.FacetStateDto; import org.opendatadiscovery.oddplatform.dto.FacetType; import org.opendatadiscovery.oddplatform.dto.QueryExampleDto; +import org.opendatadiscovery.oddplatform.dto.term.LinkedTermDto; +import org.opendatadiscovery.oddplatform.dto.term.TermRefDto; import org.opendatadiscovery.oddplatform.model.tables.pojos.DataEntityPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.DatasetFieldToTermPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.NamespacePojo; import org.opendatadiscovery.oddplatform.model.tables.pojos.QueryExamplePojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.TermPojo; import org.opendatadiscovery.oddplatform.model.tables.records.QueryExampleRecord; import org.opendatadiscovery.oddplatform.repository.util.JooqFTSHelper; import org.opendatadiscovery.oddplatform.repository.util.JooqQueryHelper; @@ -27,13 +35,17 @@ import org.springframework.stereotype.Repository; import reactor.core.publisher.Mono; +import static java.util.function.Function.identity; import static org.jooq.impl.DSL.countDistinct; import static org.jooq.impl.DSL.field; import static org.jooq.impl.DSL.jsonArrayAgg; import static org.opendatadiscovery.oddplatform.model.Tables.DATA_ENTITY; import static org.opendatadiscovery.oddplatform.model.Tables.DATA_ENTITY_TO_QUERY_EXAMPLE; +import static org.opendatadiscovery.oddplatform.model.Tables.NAMESPACE; import static org.opendatadiscovery.oddplatform.model.Tables.QUERY_EXAMPLE; import static org.opendatadiscovery.oddplatform.model.Tables.QUERY_EXAMPLE_SEARCH_ENTRYPOINT; +import static org.opendatadiscovery.oddplatform.model.Tables.QUERY_EXAMPLE_TO_TERM; +import static org.opendatadiscovery.oddplatform.model.Tables.TERM; import static org.opendatadiscovery.oddplatform.repository.util.FTSConstants.QUERY_EXAMPLE_CONDITIONS; import static org.opendatadiscovery.oddplatform.repository.util.FTSConstants.RANK_FIELD_ALIAS; @@ -43,6 +55,9 @@ public class ReactiveQueryExampleRepositoryImpl implements ReactiveQueryExampleRepository { private static final String AGG_DATA_ENTITIES_FIELD = "dataEntities"; + public static final String TERMS = "terms"; + public static final String TERM_NAMESPACES = "term_namespaces"; + public static final String TERM_RELATIONS = "term_relations"; private final JooqRecordHelper jooqRecordHelper; private final JooqFTSHelper jooqFTSHelper; @@ -122,11 +137,19 @@ public Mono> findByState(final FacetStateDto state, final .as(queryExampleSelect) .select(queryExampleCte.fields()) .select(jsonArrayAgg(field(DATA_ENTITY.asterisk().toString())).as(AGG_DATA_ENTITIES_FIELD)) + .select(jsonArrayAgg(field(TERM.asterisk().toString())).as(TERMS)) + .select(jsonArrayAgg(field(NAMESPACE.asterisk().toString())).as(TERM_NAMESPACES)) + .select(jsonArrayAgg(field(QUERY_EXAMPLE_TO_TERM.asterisk().toString())).as(TERM_RELATIONS)) .from(queryExampleCte.getName()) .leftJoin(DATA_ENTITY_TO_QUERY_EXAMPLE) .on(DATA_ENTITY_TO_QUERY_EXAMPLE.QUERY_EXAMPLE_ID.eq(queryExampleCte.field(QUERY_EXAMPLE.ID))) .leftJoin(DATA_ENTITY) .on(DATA_ENTITY.ID.eq(DATA_ENTITY_TO_QUERY_EXAMPLE.DATA_ENTITY_ID)) + .leftJoin(QUERY_EXAMPLE_TO_TERM) + .on(queryExampleCte.field(QUERY_EXAMPLE.ID).eq(QUERY_EXAMPLE_TO_TERM.QUERY_EXAMPLE_ID)) + .leftJoin(TERM) + .on(QUERY_EXAMPLE_TO_TERM.TERM_ID.eq(TERM.ID)).and(TERM.DELETED_AT.isNull()) + .leftJoin(NAMESPACE).on(TERM.NAMESPACE_ID.eq(NAMESPACE.ID)) .groupBy(queryExampleCte.fields()); return jooqReactiveOperations.flux(query) @@ -169,7 +192,8 @@ public Mono> listQueryExample(final Integer page, private QueryExampleDto mapRecordToDto(final Record record, final String cteName) { return new QueryExampleDto( jooqRecordHelper.remapCte(record, cteName, QUERY_EXAMPLE).into(QueryExamplePojo.class), - extractDataEntityPojos(record) + extractDataEntityPojos(record), + extractTerms(record) ); } @@ -177,4 +201,31 @@ private List extractDataEntityPojos(final Record r) { return new ArrayList<>(jooqRecordHelper.extractAggRelation(r, AGG_DATA_ENTITIES_FIELD, DataEntityPojo.class)); } + + private List extractTerms(final Record record) { + final Set terms = jooqRecordHelper.extractAggRelation(record, TERMS, TermPojo.class); + + final Map namespaces = jooqRecordHelper + .extractAggRelation(record, TERM_NAMESPACES, NamespacePojo.class) + .stream() + .collect(Collectors.toMap(NamespacePojo::getId, identity())); + + final Map> relations = jooqRecordHelper + .extractAggRelation(record, TERM_RELATIONS, DatasetFieldToTermPojo.class) + .stream() + .collect(Collectors.groupingBy(DatasetFieldToTermPojo::getTermId)); + + return terms.stream() + .map(pojo -> { + final TermRefDto termRefDto = TermRefDto.builder() + .term(pojo) + .namespace(namespaces.get(pojo.getNamespaceId())) + .build(); + final boolean isDescriptionLink = relations.getOrDefault(pojo.getId(), List.of()).stream() + .anyMatch(r -> Boolean.TRUE.equals(r.getIsDescriptionLink())); + + return new LinkedTermDto(termRefDto, isDescriptionLink); + }) + .toList(); + } } diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveTermQueryExampleRelationRepository.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveTermQueryExampleRelationRepository.java new file mode 100644 index 000000000..f4d8faf54 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveTermQueryExampleRelationRepository.java @@ -0,0 +1,18 @@ +package org.opendatadiscovery.oddplatform.repository.reactive; + +import org.opendatadiscovery.oddplatform.dto.QueryExampleDto; +import org.opendatadiscovery.oddplatform.model.tables.pojos.QueryExampleToTermPojo; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface ReactiveTermQueryExampleRelationRepository + extends ReactiveCRUDRepository { + + Mono createRelationWithQueryExample(final Long queryExampleId, final Long termId); + + Mono deleteRelationWithQueryExample(final Long queryExampleId, final Long termId); + + Flux removeRelationWithTermByQueryId(final Long exampleId); + + Flux getQueryExampleDatasetRelationsByTerm(final Long termId); +} diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveTermQueryExampleRelationRepositoryImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveTermQueryExampleRelationRepositoryImpl.java new file mode 100644 index 000000000..1dca66183 --- /dev/null +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveTermQueryExampleRelationRepositoryImpl.java @@ -0,0 +1,163 @@ +package org.opendatadiscovery.oddplatform.repository.reactive; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import org.jooq.Record; +import org.jooq.Record1; +import org.jooq.SelectConditionStep; +import org.jooq.SelectHavingStep; +import org.jooq.Table; +import org.jooq.impl.DSL; +import org.opendatadiscovery.oddplatform.dto.QueryExampleDto; +import org.opendatadiscovery.oddplatform.dto.term.LinkedTermDto; +import org.opendatadiscovery.oddplatform.dto.term.TermRefDto; +import org.opendatadiscovery.oddplatform.model.tables.pojos.DataEntityPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.NamespacePojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.QueryExamplePojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.QueryExampleToTermPojo; +import org.opendatadiscovery.oddplatform.model.tables.pojos.TermPojo; +import org.opendatadiscovery.oddplatform.model.tables.records.QueryExampleToTermRecord; +import org.opendatadiscovery.oddplatform.repository.util.JooqQueryHelper; +import org.opendatadiscovery.oddplatform.repository.util.JooqReactiveOperations; +import org.opendatadiscovery.oddplatform.repository.util.JooqRecordHelper; +import org.springframework.stereotype.Repository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +import static java.util.function.Function.identity; +import static org.jooq.impl.DSL.field; +import static org.jooq.impl.DSL.jsonArrayAgg; +import static org.opendatadiscovery.oddplatform.model.Tables.DATA_ENTITY; +import static org.opendatadiscovery.oddplatform.model.Tables.DATA_ENTITY_TO_QUERY_EXAMPLE; +import static org.opendatadiscovery.oddplatform.model.Tables.NAMESPACE; +import static org.opendatadiscovery.oddplatform.model.Tables.QUERY_EXAMPLE; +import static org.opendatadiscovery.oddplatform.model.Tables.QUERY_EXAMPLE_TO_TERM; +import static org.opendatadiscovery.oddplatform.model.Tables.TERM; + +@Repository +public class ReactiveTermQueryExampleRelationRepositoryImpl + extends ReactiveAbstractSoftDeleteCRUDRepository + implements ReactiveTermQueryExampleRelationRepository { + + private static final String AGG_DATA_ENTITIES_FIELD = "dataEntities"; + private static final String QUERY_EXAMPLES_CTE = "query_examples_cte"; + public static final String TERMS = "terms"; + public static final String TERM_NAMESPACES = "term_namespaces"; + public static final String TERM_RELATIONS = "term_relations"; + + private final JooqRecordHelper jooqRecordHelper; + + public ReactiveTermQueryExampleRelationRepositoryImpl(final JooqReactiveOperations jooqReactiveOperations, + final JooqQueryHelper jooqQueryHelper, + final JooqRecordHelper jooqRecordHelper) { + super(jooqReactiveOperations, jooqQueryHelper, QUERY_EXAMPLE_TO_TERM, + QueryExampleToTermPojo.class); + + this.jooqRecordHelper = jooqRecordHelper; + } + + @Override + public Mono createRelationWithQueryExample(final Long queryExampleId, final Long termId) { + final var query = DSL.insertInto(QUERY_EXAMPLE_TO_TERM) + .set(QUERY_EXAMPLE_TO_TERM.QUERY_EXAMPLE_ID, queryExampleId) + .set(QUERY_EXAMPLE_TO_TERM.TERM_ID, termId) + .onDuplicateKeyIgnore() + .returning(); + return jooqReactiveOperations.mono(query) + .map(r -> r.into(QueryExampleToTermPojo.class)); + } + + @Override + public Mono deleteRelationWithQueryExample(final Long queryExampleId, final Long termId) { + final var query = DSL.deleteFrom(QUERY_EXAMPLE_TO_TERM) + .where(QUERY_EXAMPLE_TO_TERM.QUERY_EXAMPLE_ID.eq(queryExampleId) + .and(QUERY_EXAMPLE_TO_TERM.TERM_ID.eq(termId)) + .and(QUERY_EXAMPLE_TO_TERM.IS_DESCRIPTION_LINK.isFalse())) + .returning(); + return jooqReactiveOperations.mono(query) + .map(r -> r.into(QueryExampleToTermPojo.class)); + } + + @Override + public Flux removeRelationWithTermByQueryId(final Long exampleId) { + final var query = DSL.deleteFrom(QUERY_EXAMPLE_TO_TERM) + .where(QUERY_EXAMPLE_TO_TERM.QUERY_EXAMPLE_ID.eq(exampleId)) + .returning(); + return jooqReactiveOperations.flux(query) + .map(r -> r.into(QueryExampleToTermPojo.class)); + } + + @Override + public Flux getQueryExampleDatasetRelationsByTerm(final Long termId) { + final SelectConditionStep> queryExampleSelect = + DSL.select(QUERY_EXAMPLE_TO_TERM.QUERY_EXAMPLE_ID) + .from(QUERY_EXAMPLE_TO_TERM) + .where(QUERY_EXAMPLE_TO_TERM.TERM_ID.eq(termId)); + + final Table> exampleCTE = queryExampleSelect.asTable(QUERY_EXAMPLES_CTE); + + final SelectHavingStep query = + DSL.with(exampleCTE.getName()) + .as(queryExampleSelect) + .select(QUERY_EXAMPLE.asterisk()) + .select(jsonArrayAgg(field(DATA_ENTITY.asterisk().toString())).as(AGG_DATA_ENTITIES_FIELD)) + .select(jsonArrayAgg(field(TERM.asterisk().toString())).as(TERMS)) + .select(jsonArrayAgg(field(NAMESPACE.asterisk().toString())).as(TERM_NAMESPACES)) + .select(jsonArrayAgg(field(QUERY_EXAMPLE_TO_TERM.asterisk().toString())).as(TERM_RELATIONS)) + .from(exampleCTE.getName()) + .join(QUERY_EXAMPLE) + .on(QUERY_EXAMPLE.ID.eq(exampleCTE.field(DATA_ENTITY_TO_QUERY_EXAMPLE.QUERY_EXAMPLE_ID))) + .leftJoin(DATA_ENTITY_TO_QUERY_EXAMPLE) + .on(DATA_ENTITY_TO_QUERY_EXAMPLE.QUERY_EXAMPLE_ID.eq(QUERY_EXAMPLE.ID)) + .leftJoin(DATA_ENTITY) + .on(DATA_ENTITY.ID.eq(DATA_ENTITY_TO_QUERY_EXAMPLE.DATA_ENTITY_ID)) + .leftJoin(QUERY_EXAMPLE_TO_TERM) + .on(QUERY_EXAMPLE.ID.eq(QUERY_EXAMPLE_TO_TERM.QUERY_EXAMPLE_ID)) + .leftJoin(TERM) + .on(QUERY_EXAMPLE_TO_TERM.TERM_ID.eq(TERM.ID)).and(TERM.DELETED_AT.isNull()) + .leftJoin(NAMESPACE).on(TERM.NAMESPACE_ID.eq(NAMESPACE.ID)) + .groupBy(QUERY_EXAMPLE.ID); + + return jooqReactiveOperations.flux(query) + .map(r -> new QueryExampleDto(r.into(QUERY_EXAMPLE).into(QueryExamplePojo.class), + extractDataEntityPojos(r), + extractTerms(r) + )); + } + + private List extractDataEntityPojos(final Record r) { + return new ArrayList<>(jooqRecordHelper.extractAggRelation(r, AGG_DATA_ENTITIES_FIELD, + DataEntityPojo.class)); + } + + private List extractTerms(final Record record) { + final Set terms = jooqRecordHelper.extractAggRelation(record, TERMS, TermPojo.class); + + final Map namespaces = jooqRecordHelper + .extractAggRelation(record, TERM_NAMESPACES, NamespacePojo.class) + .stream() + .collect(Collectors.toMap(NamespacePojo::getId, identity())); + + final Map> relations = jooqRecordHelper + .extractAggRelation(record, TERM_RELATIONS, QueryExampleToTermPojo.class) + .stream() + .collect(Collectors.groupingBy(QueryExampleToTermPojo::getTermId)); + + return terms.stream() + .map(pojo -> { + final TermRefDto termRefDto = TermRefDto.builder() + .term(pojo) + .namespace(namespaces.get(pojo.getNamespaceId())) + .build(); + final boolean isDescriptionLink = relations.getOrDefault(pojo.getId(), List.of()).stream() + .anyMatch(r -> Boolean.TRUE.equals(r.getIsDescriptionLink())); + + return new LinkedTermDto(termRefDto, isDescriptionLink); + }) + .toList(); + } +} + diff --git a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveTermRepositoryImpl.java b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveTermRepositoryImpl.java index 0e735e2b1..403892de4 100644 --- a/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveTermRepositoryImpl.java +++ b/odd-platform-api/src/main/java/org/opendatadiscovery/oddplatform/repository/reactive/ReactiveTermRepositoryImpl.java @@ -57,6 +57,7 @@ import static org.opendatadiscovery.oddplatform.model.Tables.DATA_ENTITY_TO_TERM; import static org.opendatadiscovery.oddplatform.model.Tables.NAMESPACE; import static org.opendatadiscovery.oddplatform.model.Tables.OWNER; +import static org.opendatadiscovery.oddplatform.model.Tables.QUERY_EXAMPLE_TO_TERM; import static org.opendatadiscovery.oddplatform.model.Tables.TAG; import static org.opendatadiscovery.oddplatform.model.Tables.TAG_TO_TERM; import static org.opendatadiscovery.oddplatform.model.Tables.TERM; @@ -77,6 +78,7 @@ public class ReactiveTermRepositoryImpl extends ReactiveAbstractSoftDeleteCRUDRe private static final String AGG_TAGS_FIELD = "tags"; private static final String ENTITIES_COUNT = "entities_count"; private static final String COLUMNS_COUNT = "columns_count"; + private static final String QUERY_EXAMPLE_COUNT = "query_example_count"; private static final String IS_DESCRIPTION_LINK = "is_description_link"; private final JooqRecordHelper jooqRecordHelper; @@ -187,6 +189,7 @@ public Mono getTermDetailsDto(final Long id) { .select(jsonArrayAgg(field(TAG.asterisk().toString())).as(AGG_TAGS_FIELD)) .select(DSL.countDistinct(DATA_ENTITY_TO_TERM.DATA_ENTITY_ID).as(ENTITIES_COUNT)) .select(DSL.countDistinct(DATASET_FIELD_TO_TERM.DATASET_FIELD_ID).as(COLUMNS_COUNT)) + .select(DSL.countDistinct(QUERY_EXAMPLE_TO_TERM.QUERY_EXAMPLE_ID).as(QUERY_EXAMPLE_COUNT)) .from(TERM) .join(NAMESPACE).on(NAMESPACE.ID.eq(TERM.NAMESPACE_ID)) .leftJoin(TERM_OWNERSHIP).on(TERM_OWNERSHIP.TERM_ID.eq(TERM.ID)) @@ -196,6 +199,7 @@ public Mono getTermDetailsDto(final Long id) { .leftJoin(TAG).on(TAG_TO_TERM.TAG_ID.eq(TAG.ID)) .leftJoin(DATA_ENTITY_TO_TERM).on(DATA_ENTITY_TO_TERM.TERM_ID.eq(TERM.ID)) .leftJoin(DATASET_FIELD_TO_TERM).on(DATASET_FIELD_TO_TERM.TERM_ID.eq(TERM.ID)) + .leftJoin(QUERY_EXAMPLE_TO_TERM).on(QUERY_EXAMPLE_TO_TERM.TERM_ID.eq(TERM.ID)) .where(TERM.ID.eq(id).and(TERM.DELETED_AT.isNull())) .groupBy(groupByFields); return jooqReactiveOperations.mono(query) @@ -295,6 +299,7 @@ public Mono> findByState(final FacetStateDto state, final int page .select(jsonArrayAgg(field(TITLE.asterisk().toString())).as(AGG_TITLES_FIELD)) .select(DSL.countDistinct(DATA_ENTITY_TO_TERM.DATA_ENTITY_ID).as(ENTITIES_COUNT)) .select(DSL.countDistinct(DATASET_FIELD_TO_TERM.DATASET_FIELD_ID).as(COLUMNS_COUNT)) + .select(DSL.countDistinct(QUERY_EXAMPLE_TO_TERM.QUERY_EXAMPLE_ID).as(QUERY_EXAMPLE_COUNT)) .from(termCTE.getName()) .join(NAMESPACE).on(NAMESPACE.ID.eq(termCTE.field(TERM.NAMESPACE_ID))) .leftJoin(TERM_OWNERSHIP).on(TERM_OWNERSHIP.TERM_ID.eq(termCTE.field(TERM.ID))) @@ -302,6 +307,7 @@ public Mono> findByState(final FacetStateDto state, final int page .leftJoin(TITLE).on(TITLE.ID.eq(TERM_OWNERSHIP.TITLE_ID)) .leftJoin(DATA_ENTITY_TO_TERM).on(DATA_ENTITY_TO_TERM.TERM_ID.eq(termCTE.field(TERM.ID))) .leftJoin(DATASET_FIELD_TO_TERM).on(DATASET_FIELD_TO_TERM.TERM_ID.eq(termCTE.field(TERM.ID))) + .leftJoin(QUERY_EXAMPLE_TO_TERM).on(QUERY_EXAMPLE_TO_TERM.TERM_ID.eq(termCTE.field(TERM.ID))) .groupBy(groupByFields); return jooqReactiveOperations.flux(query) @@ -412,6 +418,7 @@ private TermDto mapRecordToDto(final Record record) { .termRefDto(refDto) .entitiesUsingCount(record.get(ENTITIES_COUNT, Integer.class)) .columnsUsingCount(record.get(COLUMNS_COUNT, Integer.class)) + .queryExampleUsingCount(record.get(QUERY_EXAMPLE_COUNT, Integer.class)) .ownerships(extractOwnershipRelation(record)) .build(); } @@ -422,6 +429,7 @@ private TermDto mapRecordToDto(final Record record, final String cteName) { .termRefDto(refDto) .entitiesUsingCount(record.get(ENTITIES_COUNT, Integer.class)) .columnsUsingCount(record.get(COLUMNS_COUNT, Integer.class)) + .queryExampleUsingCount(record.get(QUERY_EXAMPLE_COUNT, Integer.class)) .ownerships(extractOwnershipRelation(record)) .build(); } 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 7b9fcdda0..cf330bd53 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 @@ -5,6 +5,7 @@ import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleFormData; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleList; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleRefList; +import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleTermFormData; import reactor.core.publisher.Mono; public interface QueryExampleService { @@ -24,4 +25,10 @@ Mono createQueryExampleToDatasetRelationship( Mono getQueryExampleDetails(final Long exampleId); Mono getQueryExampleList(final Integer page, final Integer size, final String query); + + Mono linkTermWithQueryExample(final Long termId, final QueryExampleTermFormData item); + + Mono removeTermFromQueryExample(final Long termId, final Long exampleId); + + Mono getQueryExampleByTermId(final Long termId); } 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 be5f25703..4ec9adaa3 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 @@ -11,6 +11,7 @@ import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleFormData; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleList; import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleRefList; +import org.opendatadiscovery.oddplatform.api.contract.model.QueryExampleTermFormData; import org.opendatadiscovery.oddplatform.dto.QueryExampleDto; import org.opendatadiscovery.oddplatform.exception.BadUserRequestException; import org.opendatadiscovery.oddplatform.exception.NotFoundException; @@ -20,6 +21,7 @@ import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveDataEntityQueryExampleRelationRepository; import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveQueryExampleRepository; import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveQueryExampleSearchEntrypointRepository; +import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveTermQueryExampleRelationRepository; import org.springframework.stereotype.Service; import reactor.core.publisher.Mono; import reactor.util.function.Tuple2; @@ -30,6 +32,7 @@ public class QueryExampleServiceImpl implements QueryExampleService { private final ReactiveQueryExampleRepository queryExampleRepository; private final ReactiveQueryExampleSearchEntrypointRepository queryExampleSearchEntrypointRepository; private final ReactiveDataEntityQueryExampleRelationRepository dataEntityToQueryExampleRepository; + private final ReactiveTermQueryExampleRelationRepository termQueryExampleRelationRepository; private final DataEntityService dataEntityService; private final QueryExampleMapper queryExampleMapper; @@ -38,7 +41,8 @@ public class QueryExampleServiceImpl implements QueryExampleService { public Mono createQueryExample(final QueryExampleFormData queryExampleFormData) { final QueryExamplePojo pojo = queryExampleMapper.mapToPojo(queryExampleFormData); return queryExampleRepository.create(pojo) - .map(queryExamplePojo -> queryExampleMapper.mapToQueryExampleDetails(queryExamplePojo, List.of())) + .map( + queryExamplePojo -> queryExampleMapper.mapToQueryExampleDetails(queryExamplePojo, List.of(), List.of())) .flatMap(this::updateSearchVectors); } @@ -61,7 +65,8 @@ public Mono createQueryExampleToDatasetRelationship( .then(dataEntityToQueryExampleRepository.getQueryExampleDatasetRelations(queryExampleId)) .map(dto -> queryExampleMapper.mapToQueryExample( dto.queryExamplePojo(), - dto.linkedEntities())) + dto.linkedEntities(), + dto.linkedTerms())) .zipWith(queryExampleSearchEntrypointRepository.updateQueryExampleVectorsForDataEntity(queryExampleId)) .map(Tuple2::getT1); } @@ -81,6 +86,7 @@ public Mono deleteQueryExample(final Long exampleId) { return queryExampleRepository.get(exampleId) .switchIfEmpty(Mono.error(() -> new NotFoundException("QueryExample", exampleId))) .thenMany(dataEntityToQueryExampleRepository.removeRelationWithDataEntityByQueryId(exampleId)) + .thenMany(termQueryExampleRelationRepository.removeRelationWithTermByQueryId(exampleId)) .then(queryExampleRepository.delete(exampleId).map(QueryExamplePojo::getId)) .then(); } @@ -101,8 +107,7 @@ public Mono getQueryExampleDetails(final Long exampleId) { .map(dto -> dataEntityService .getDimensionsByIds(getRelatedDataEntitiesIds(dto)) .map(items -> queryExampleMapper.mapToQueryExampleDetails( - dto.queryExamplePojo(), - items))) + dto.queryExamplePojo(), items, dto.linkedTerms()))) .flatMap(Function.identity()); } @@ -112,14 +117,41 @@ public Mono getQueryExampleList(final Integer page, final I .map(queryExampleMapper::mapToRefPage); } + @Override + @ReactiveTransactional + public Mono linkTermWithQueryExample(final Long termId, final QueryExampleTermFormData item) { + return termQueryExampleRelationRepository.createRelationWithQueryExample(item.getQueryExampleId(), termId) + .switchIfEmpty(Mono.error(() -> new BadUserRequestException("Term is assigned to Query Example"))) + .then(dataEntityToQueryExampleRepository.getQueryExampleDatasetRelations(item.getQueryExampleId())) + .map(dto -> queryExampleMapper.mapToQueryExample(dto.queryExamplePojo(), dto.linkedEntities(), + dto.linkedTerms())) + .zipWith( + queryExampleSearchEntrypointRepository.updateQueryExampleVectorsForDataEntity(item.getQueryExampleId())) + .map(Tuple2::getT1); + } + + @Override + @ReactiveTransactional + public Mono removeTermFromQueryExample(final Long termId, final Long exampleId) { + return termQueryExampleRelationRepository.deleteRelationWithQueryExample(exampleId, termId) + .then(); + } + + @Override + public Mono getQueryExampleByTermId(final Long termId) { + return termQueryExampleRelationRepository + .getQueryExampleDatasetRelationsByTerm(termId) + .collectList() + .map(queryExampleMapper::mapListToQueryExampleList); + } + private Mono update(final QueryExamplePojo pojo) { return queryExampleRepository.update(pojo) .flatMap(item -> dataEntityToQueryExampleRepository.getQueryExampleDatasetRelations(item.getId())) .map(dto -> dataEntityService .getDimensionsByIds(getRelatedDataEntitiesIds(dto)) .map(items -> queryExampleMapper.mapToQueryExampleDetails( - dto.queryExamplePojo(), - items))) + dto.queryExamplePojo(), items, dto.linkedTerms()))) .flatMap(Function.identity()); } diff --git a/odd-platform-api/src/main/resources/db/migration/V0_0_90__added_query_example_to_term_relations.sql b/odd-platform-api/src/main/resources/db/migration/V0_0_90__added_query_example_to_term_relations.sql new file mode 100644 index 000000000..345510adf --- /dev/null +++ b/odd-platform-api/src/main/resources/db/migration/V0_0_90__added_query_example_to_term_relations.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS query_example_to_term +( + query_example_id bigint NOT NULL, + term_id bigint NOT NULL, + is_description_link boolean DEFAULT FALSE, + deleted_at TIMESTAMP WITHOUT TIME ZONE, + + CONSTRAINT query_example_pk PRIMARY KEY (query_example_id, term_id), + + CONSTRAINT query_example_to_term_query_example_id_fkey FOREIGN KEY (query_example_id) REFERENCES query_example (id), + CONSTRAINT query_example_term_id_fkey FOREIGN KEY (term_id) REFERENCES term (id) +); \ No newline at end of file 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 b922138cf..deab4dac2 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 @@ -48,6 +48,7 @@ import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveDataEntityQueryExampleRelationRepository; import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveQueryExampleRepository; import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveQueryExampleSearchEntrypointRepository; +import org.opendatadiscovery.oddplatform.repository.reactive.ReactiveTermQueryExampleRelationRepository; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; @@ -69,6 +70,8 @@ public class QueryExampleServiceTest { @Mock private ReactiveDataEntityQueryExampleRelationRepository dataEntityToQueryExampleRepository; @Mock + private ReactiveTermQueryExampleRelationRepository termQueryExampleRelationRepository; + @Mock private DataEntityService dataEntityService; private final QueryExampleMapper queryExampleMapper = new QueryExampleMapperImpl(); @@ -84,6 +87,7 @@ void setUp() { ); queryExampleMapper.setDateTimeMapper(new DateTimeMapperImpl()); + queryExampleMapper.setTermMapper(termMapper); queryExampleMapper.setDataEntityMapper( new DataEntityMapperImpl( new DataSourceMapperImpl( @@ -122,6 +126,7 @@ void setUp() { queryExampleService = new QueryExampleServiceImpl(queryExampleRepository, queryExampleSearchEntrypointRepository, dataEntityToQueryExampleRepository, + termQueryExampleRelationRepository, dataEntityService, queryExampleMapper); } @@ -208,6 +213,8 @@ public void deleteQueryExampleTest(final Long queryExampleId, when(queryExampleRepository.delete(anyLong())).thenReturn(Mono.empty()); when(dataEntityToQueryExampleRepository.removeRelationWithDataEntityByQueryId(anyLong())) .thenReturn(Flux.empty()); + when(termQueryExampleRelationRepository.removeRelationWithTermByQueryId(anyLong())) + .thenReturn(Flux.empty()); queryExampleService .deleteQueryExample(queryExampleId) @@ -301,7 +308,8 @@ private static Stream queryExampleRelationProvider() { .setId(1L) .setQuery("select 1 from dual") .setDefinition("def"), - List.of(new DataEntityPojo().setId(1L).setStatus((short) 3)) + List.of(new DataEntityPojo().setId(1L).setStatus((short) 3)), + List.of() ), new QueryExample() .query("select 1 from dual") @@ -323,7 +331,8 @@ private static Stream queryExampleDetailsProvider() { pojo, new QueryExampleDto( pojo, - List.of(new DataEntityPojo().setId(1L).setStatus((short) 3)) + List.of(new DataEntityPojo().setId(1L).setStatus((short) 3)), + List.of() ), DataEntityDimensionsDto .dimensionsBuilder() diff --git a/odd-platform-specification/components.yaml b/odd-platform-specification/components.yaml index 3e6ad5b7b..24025cb6e 100644 --- a/odd-platform-specification/components.yaml +++ b/odd-platform-specification/components.yaml @@ -189,6 +189,8 @@ components: - QUERY_EXAMPLE_DELETE - QUERY_EXAMPLE_DATASET_CREATE - QUERY_EXAMPLE_DATASET_DELETE + - QUERY_EXAMPLE_TERM_CREATE + - QUERY_EXAMPLE_TERM_DELETE - LOOKUP_TABLE_CREATE - LOOKUP_TABLE_UPDATE - LOOKUP_TABLE_DELETE @@ -2496,6 +2498,14 @@ components: authType: type: string + LinkedTermList: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/LinkedTerm' + LinkedTerm: type: object properties: @@ -2562,6 +2572,8 @@ components: type: integer columns_using_count: type: integer + query_example_using_count: + type: integer created_at: type: string format: date-time @@ -2612,6 +2624,15 @@ components: required: - term_id + QueryExampleTermFormData: + type: object + properties: + query_example_id: + type: integer + format: int64 + required: + - query_example_id + TermSearchFormData: type: object properties: @@ -2722,11 +2743,14 @@ components: type: string linked_entities: $ref: '#/components/schemas/DataEntityRefList' + linked_terms: + $ref: '#/components/schemas/LinkedTermList' required: - id - definition - query - linked_entities + - linked_terms QueryExampleDetails: allOf: @@ -2735,6 +2759,8 @@ components: properties: linked_entities: $ref: '#/components/schemas/DataEntityList' + linked_terms: + $ref: '#/components/schemas/LinkedTermList' created_at: type: string format: date-time @@ -2743,6 +2769,7 @@ components: format: date-time required: - linked_entities + - linked_terms - created_at - updated_at diff --git a/odd-platform-specification/openapi.yaml b/odd-platform-specification/openapi.yaml index ed08d377a..e8cd7074a 100644 --- a/odd-platform-specification/openapi.yaml +++ b/odd-platform-specification/openapi.yaml @@ -2283,6 +2283,23 @@ paths: tags: - queryExample + /api/queryexample/term/{term_id}: + get: + summary: Get QueryExample + description: Gets QueryExamples by Term's id + operationId: getQueryExampleByTermId + parameters: + - $ref: './components.yaml/#/components/parameters/TermIdParam' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: './components.yaml/#/components/schemas/QueryExampleList' + tags: + - queryExample + /api/queryexample/search/suggestions: get: summary: Get QueryExample search suggestions by query @@ -2835,6 +2852,7 @@ paths: $ref: './components.yaml/#/components/schemas/DataEntityList' tags: - term + /api/terms/{term_id}/linked_columns: get: summary: Get term linked columns @@ -2855,6 +2873,43 @@ paths: tags: - term + /api/terms/{term_id}/queryexample: + post: + summary: add Term to QueryExample + description: Creates a new relation between QueryExample and Term + operationId: createQueryExampleToTermRelationship + parameters: + - $ref: './components.yaml/#/components/parameters/TermIdParam' + requestBody: + required: true + content: + application/json: + schema: + $ref: './components.yaml/#/components/schemas/QueryExampleTermFormData' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: './components.yaml/#/components/schemas/QueryExample' + tags: + - term + + /api/terms/{term_id}/queryexample/{example_id}: + delete: + summary: Delete Term from QueryExample + description: Deletes relationship between QueryExample and Term + operationId: deleteQueryExampleToTermRelationship + parameters: + - $ref: './components.yaml/#/components/parameters/TermIdParam' + - $ref: './components.yaml/#/components/parameters/QueryExampleIdParam' + responses: + '204': + $ref: './components.yaml/#/components/responses/Deleted' + tags: + - term + /api/terms/search: post: summary: Terms search by query diff --git a/odd-platform-ui/src/components/DataEntityDetails/DataEntityQueryExamples/DataEntityDetailsQueryExamples.tsx b/odd-platform-ui/src/components/DataEntityDetails/DataEntityQueryExamples/DataEntityDetailsQueryExamples.tsx index ee751ce51..79d56620a 100644 --- a/odd-platform-ui/src/components/DataEntityDetails/DataEntityQueryExamples/DataEntityDetailsQueryExamples.tsx +++ b/odd-platform-ui/src/components/DataEntityDetails/DataEntityQueryExamples/DataEntityDetailsQueryExamples.tsx @@ -49,7 +49,7 @@ const DataEntityDetailsQueryExamples: React.FC = () => { ))} {isLoading && } diff --git a/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QuertExampleDetailsLinkedTermsItem.tsx b/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QuertExampleDetailsLinkedTermsItem.tsx new file mode 100644 index 000000000..0da199ed7 --- /dev/null +++ b/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QuertExampleDetailsLinkedTermsItem.tsx @@ -0,0 +1,49 @@ +import type { TermRef } from 'generated-sources'; +import { Box, Grid, Typography } from '@mui/material'; +import React, { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { termDetailsPath } from 'routes'; + +interface QuertExampleDetailsLinkedTermsItemProps { + term: TermRef; +} + +const QuertExampleDetailsLinkedTermsItem = ({ + term, +}: QuertExampleDetailsLinkedTermsItemProps) => { + const navigate = useNavigate(); + const onClick = useCallback(() => { + navigate(termDetailsPath(term.id)); + }, [term.id, navigate]); + + return ( + ({ + borderBottom: `1px solid ${theme.palette.divider}`, + padding: theme.spacing(1.25, 0), + ':hover': { + backgroundColor: `${theme.palette.backgrounds.primary}`, + cursor: 'pointer', + }, + })} + wrap='nowrap' + > + + + + {term.name} + + + + + + {term.namespace.name} + + + + ); +}; + +export default QuertExampleDetailsLinkedTermsItem; diff --git a/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsContainer.tsx b/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsContainer.tsx index 59c72dbcd..71e977605 100644 --- a/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsContainer.tsx +++ b/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsContainer.tsx @@ -8,9 +8,11 @@ import { useQueryExamplesRouteParams } from 'routes'; import { WithPermissionsProvider } from 'components/shared/contexts'; import { Permission, PermissionResourceType } from 'generated-sources'; import { useResourcePermissions } from 'lib/hooks/api/permissions'; +import { useSearchParams } from 'react-router-dom'; import QueryExampleDetailsTabs from './QueryExampleDetailsTabs'; import QueryExampleDetailsOverview from './QueryExampleDetailsOverview'; import QueryExampleDetailsLinkedEntities from './QueryExampleDetailsLinkedEntities'; +import QueryExampleDetailsLinkedTerms from './QueryExampleDetailsLinkedTerms'; import { QueryExampleDetailsContainerActions } from './QueryExampleDetailsContainerActions'; const QueryExampleDetailsContainer: React.FC = () => { @@ -25,10 +27,8 @@ const QueryExampleDetailsContainer: React.FC = () => { exampleId, }); - const [selectedTab, setSelectedTab] = useState(0); - const handleTabChange = useCallback(() => { - setSelectedTab(prev => (prev === 0 ? 1 : 0)); - }, []); + const [searchParams] = useSearchParams(); + const tab = searchParams.get('tab') ?? 'overview'; const { formatDistanceToNowStrict } = useAppDateTime(); @@ -66,23 +66,33 @@ const QueryExampleDetailsContainer: React.FC = () => { - {selectedTab === 0 && ( + {tab === 'overview' && ( )} - {selectedTab === 1 && ( + {tab === 'linked-entities' && ( )} + {tab === 'linked-terms' && ( + term.term) + : [] + } + /> + )} ) : ( diff --git a/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsLinkedTerms.tsx b/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsLinkedTerms.tsx new file mode 100644 index 000000000..095b70ef0 --- /dev/null +++ b/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsLinkedTerms.tsx @@ -0,0 +1,40 @@ +import { Grid, Typography } from '@mui/material'; +import type { TermRef } from 'generated-sources'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { EmptyContentPlaceholder } from 'components/shared/elements'; +import QueryExampleDetailsLinkedTermsItem from './QuertExampleDetailsLinkedTermsItem'; + +interface QueryExampleDetailsLinkedTermsProps { + terms: TermRef[]; +} + +const QueryExampleDetailsLinkedTerms = ({ + terms, +}: QueryExampleDetailsLinkedTermsProps) => { + const { t } = useTranslation(); + return ( + + ({ + borderBottom: `1px solid ${theme.palette.divider}`, + })} + wrap='nowrap' + > + + {t('Name')} + + + {t('Namespace')} + + + {terms.map(term => ( + + ))} + {terms.length === 0 && } + + ); +}; + +export default QueryExampleDetailsLinkedTerms; diff --git a/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsTabs.tsx b/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsTabs.tsx index 9c36416c4..5edc0b23a 100644 --- a/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsTabs.tsx +++ b/odd-platform-ui/src/components/DataModelling/QueryExampleDetails/QueryExampleDetailsTabs.tsx @@ -1,40 +1,64 @@ -import { useTranslation } from 'react-i18next'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import type { AppTabItem } from 'components/shared/elements'; import { AppTabs } from 'components/shared/elements'; +import { useSearchParams } from 'react-router-dom'; interface QueryExampleDetailsTabsProps { - onHandleTabChange: () => void; linkedEntitiesHint?: number | string; - selectedTab?: number; + linkedTermsHint?: number | string; } const QueryExampleDetailsTabs = ({ linkedEntitiesHint, - selectedTab, - onHandleTabChange, + linkedTermsHint, }: QueryExampleDetailsTabsProps) => { - const { t } = useTranslation(); - - const tabs = useMemo( + const tabs = useMemo[]>( () => [ { - name: t('Overview'), + name: 'Overview', + value: 'overview', }, { - name: t('Linked Entities'), + name: 'Linked Entities', + value: 'linked-entities', hint: linkedEntitiesHint, }, + { + name: 'Linked Terms', + value: 'linked-terms', + hint: linkedTermsHint, + }, ], - [t, linkedEntitiesHint] + [linkedEntitiesHint, linkedTermsHint] + ); + + const [searchParams, setSearchParams] = useSearchParams(); + + const findInitTabIdx = useCallback(() => { + const type = searchParams.get('tab') ?? 'overview'; + return tabs.findIndex(tab => tab.value === type); + }, [searchParams, tabs]); + + const initialTabIdx = useMemo(() => findInitTabIdx(), [findInitTabIdx, searchParams]); + + const onTabChange = useCallback( + (newTab: number) => { + const type = tabs[newTab].value; + if (type) { + const params = new URLSearchParams(searchParams); + params.set('tab', type); + setSearchParams(params); + } + }, + [searchParams, tabs] ); return ( ); }; diff --git a/odd-platform-ui/src/components/Terms/TermDetails/TermDetailsRoutes/TermDetailsRoutes.tsx b/odd-platform-ui/src/components/Terms/TermDetails/TermDetailsRoutes/TermDetailsRoutes.tsx index a4ce779a0..d5cc383b7 100644 --- a/odd-platform-ui/src/components/Terms/TermDetails/TermDetailsRoutes/TermDetailsRoutes.tsx +++ b/odd-platform-ui/src/components/Terms/TermDetails/TermDetailsRoutes/TermDetailsRoutes.tsx @@ -1,6 +1,10 @@ import React, { lazy } from 'react'; import { Route, Routes, Navigate } from 'react-router-dom'; import { AppSuspenseWrapper } from 'components/shared/elements'; +import { Permission, PermissionResourceType } from 'generated-sources'; +import { WithPermissionsProvider } from 'components/shared/contexts'; +import { useTermsRouteParams } from 'routes'; +import { useResourcePermissions } from 'lib/hooks/api/permissions'; const Overview = lazy(() => import('../Overview/Overview')); const LinkedEntitiesList = lazy( @@ -9,16 +13,38 @@ const LinkedEntitiesList = lazy( const LinkedColumnsList = lazy( () => import('../TermLinkedColumnsList/LinkedColumnsList') ); +const TermQueryExamples = lazy(() => import('../TermQueryExamples/TermQueryExamples')); -const TermDetailsRoutes: React.FC = () => ( - - - } /> - } /> - } /> - } /> - - -); +const TermDetailsRoutes: React.FC = () => { + const { termId } = useTermsRouteParams(); + const { data: resourcePermissions } = useResourcePermissions({ + resourceId: termId, + permissionResourceType: PermissionResourceType.TERM, + }); + + return ( + + + } /> + } /> + } /> + + } + /> + } /> + + + ); +}; export default TermDetailsRoutes; diff --git a/odd-platform-ui/src/components/Terms/TermDetails/TermDetailsTabs/TermDetailsTabs.tsx b/odd-platform-ui/src/components/Terms/TermDetails/TermDetailsTabs/TermDetailsTabs.tsx index 0083067eb..ade380662 100644 --- a/odd-platform-ui/src/components/Terms/TermDetails/TermDetailsTabs/TermDetailsTabs.tsx +++ b/odd-platform-ui/src/components/Terms/TermDetails/TermDetailsTabs/TermDetailsTabs.tsx @@ -29,6 +29,10 @@ const TermDetailsTabs: React.FC = () => { hint: termDetails?.columnsUsingCount, hidden: !termDetails?.columnsUsingCount, }, + { + name: 'Query examples', + link: termDetailsPath(termId, 'query-examples'), + }, ], [termId, termDetails?.entitiesUsingCount, termDetails?.columnsUsingCount, t] ); diff --git a/odd-platform-ui/src/components/Terms/TermDetails/TermQueryExamples/AssignTermQueryExampleForm.tsx b/odd-platform-ui/src/components/Terms/TermDetails/TermQueryExamples/AssignTermQueryExampleForm.tsx new file mode 100644 index 000000000..620a0fb18 --- /dev/null +++ b/odd-platform-ui/src/components/Terms/TermDetails/TermQueryExamples/AssignTermQueryExampleForm.tsx @@ -0,0 +1,87 @@ +import React, { cloneElement } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Controller, useForm } from 'react-hook-form'; +import { Typography } from '@mui/material'; +import QueryExamplesAutocomplete from 'components/shared/elements/QueryExamples/QueryExamplesAutocomplete'; +import Button from 'components/shared/elements/Button/Button'; +import DialogWrapper from 'components/shared/elements/DialogWrapper/DialogWrapper'; +import { useAssignTermQueryExample } from 'lib/hooks'; + +interface AssignTermQueryFormProps { + openBtnEl: JSX.Element; + termId: number; +} + +type FormData = { + exampleId: number; +}; + +const AssignTermQueryExampleForm: React.FC = ({ + openBtnEl, + termId, +}) => { + const { t } = useTranslation(); + const { mutateAsync, isSuccess } = useAssignTermQueryExample(termId); + const formId = 'assign-term-query-example-form'; + + const { handleSubmit, control, reset, formState } = useForm({ + mode: 'onChange', + reValidateMode: 'onChange', + }); + + const onSubmit = ({ exampleId }: FormData) => { + mutateAsync({ + queryExampleTermFormData: { + queryExampleId: exampleId, + }, + termId, + }).then(_ => { + reset(); + }); + }; + + const title = ( + + {t('Add query example')} + + ); + + const formContent = () => ( +
+ + {t('Select a query example from the list')}. + + } + /> + + ); + + const actionButton = () => ( +