diff --git a/README.md b/README.md index f0314c0ed90e1..d2208cf6ced49 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,9 @@ If you're looking to build & modify datahub please take a look at our [Developme - [acryldata/datahub-actions](https://github.com/acryldata/datahub-actions): DataHub Actions is a framework for responding to changes to your DataHub Metadata Graph in real time. - [acryldata/datahub-helm](https://github.com/acryldata/datahub-helm): Repository of helm charts for deploying DataHub on a Kubernetes cluster - [acryldata/meta-world](https://github.com/acryldata/meta-world): A repository to store recipes, custom sources, transformations and other things to make your DataHub experience magical +- [dbt-impact-action](https://github.com/acryldata/dbt-impact-action) : This repository contains a github action for commenting on your PRs with a summary of the impact of changes within a dbt project +- [datahub-tools](https://github.com/makenotion/datahub-tools) : Additional python tools to interact with the DataHub GraphQL endpoints, built by Notion +- [business-glossary-sync-action](https://github.com/acryldata/business-glossary-sync-action) : This repository contains a github action that opens PRs to update your business glossary yaml file. ## Releases diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/resolver/GetMetadataAnalyticsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/resolver/GetMetadataAnalyticsResolver.java index 45adba30c23d1..f61c2eb77739b 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/resolver/GetMetadataAnalyticsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/analytics/resolver/GetMetadataAnalyticsResolver.java @@ -78,7 +78,7 @@ private List getCharts(MetadataAnalyticsInput input, Authenticat } SearchResult searchResult = _entityClient.searchAcrossEntities(entities, query, filter, 0, 0, - null, authentication); + null, null, authentication); List aggregationMetadataList = searchResult.getMetadata().getAggregations(); diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/assertion/DeleteAssertionResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/assertion/DeleteAssertionResolver.java index 1d720535d3395..8006ae7d2a464 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/assertion/DeleteAssertionResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/assertion/DeleteAssertionResolver.java @@ -9,11 +9,11 @@ import com.datahub.authorization.DisjunctivePrivilegeGroup; import com.linkedin.datahub.graphql.exception.AuthorizationException; import com.linkedin.datahub.graphql.resolvers.AuthUtils; -import com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils; import com.linkedin.entity.client.EntityClient; import com.linkedin.metadata.Constants; import com.linkedin.metadata.authorization.PoliciesConfig; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import java.util.concurrent.CompletableFuture; @@ -74,7 +74,7 @@ private boolean isAuthorizedToDeleteAssertion(final QueryContext context, final // 2. fetch the assertion info AssertionInfo info = - (AssertionInfo) MutationUtils.getAspectFromEntity( + (AssertionInfo) EntityUtils.getAspectFromEntity( assertionUrn.toString(), Constants.ASSERTION_INFO_ASPECT_NAME, _entityService, null); if (info != null) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/container/ContainerEntitiesResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/container/ContainerEntitiesResolver.java index 100fef2f62d68..4b8bd37a4fabe 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/container/ContainerEntitiesResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/container/ContainerEntitiesResolver.java @@ -86,6 +86,7 @@ public CompletableFuture get(final DataFetchingEnvironment enviro start, count, null, + null, context.getAuthentication() )); diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataproduct/ListDataProductAssetsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataproduct/ListDataProductAssetsResolver.java index f7db6e56940d7..e727ebe185838 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataproduct/ListDataProductAssetsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/dataproduct/ListDataProductAssetsResolver.java @@ -126,6 +126,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) start, count, searchFlags, + null, ResolverUtils.getAuthentication(environment))); } catch (Exception e) { log.error( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/deprecation/UpdateDeprecationResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/deprecation/UpdateDeprecationResolver.java index b132f74adc0f0..75c09d0cf7e43 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/deprecation/UpdateDeprecationResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/deprecation/UpdateDeprecationResolver.java @@ -14,6 +14,7 @@ import com.linkedin.entity.client.EntityClient; import com.linkedin.metadata.authorization.PoliciesConfig; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.mxe.MetadataChangeProposal; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; @@ -23,7 +24,7 @@ import lombok.extern.slf4j.Slf4j; import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*; -import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.*; +import com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils; import static com.linkedin.metadata.Constants.*; @@ -55,7 +56,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw _entityService ); try { - Deprecation deprecation = (Deprecation) getAspectFromEntity( + Deprecation deprecation = (Deprecation) EntityUtils.getAspectFromEntity( entityUrn.toString(), DEPRECATION_ASPECT_NAME, _entityService, @@ -63,7 +64,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw updateDeprecation(deprecation, input, context); // Create the Deprecation aspect - final MetadataChangeProposal proposal = buildMetadataChangeProposalWithUrn(entityUrn, DEPRECATION_ASPECT_NAME, deprecation); + final MetadataChangeProposal proposal = MutationUtils.buildMetadataChangeProposalWithUrn(entityUrn, DEPRECATION_ASPECT_NAME, deprecation); _entityClient.ingestProposal(proposal, context.getAuthentication(), false); return true; } catch (Exception e) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/domain/DomainEntitiesResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/domain/DomainEntitiesResolver.java index e4bcc94137f6a..06bfa36fc3c14 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/domain/DomainEntitiesResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/domain/DomainEntitiesResolver.java @@ -81,6 +81,7 @@ public CompletableFuture get(final DataFetchingEnvironment enviro start, count, null, + null, context.getAuthentication() )); diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/domain/SetDomainResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/domain/SetDomainResolver.java index 3936bfb793cd1..56a76dcb1e07f 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/domain/SetDomainResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/domain/SetDomainResolver.java @@ -8,6 +8,7 @@ import com.linkedin.domain.Domains; import com.linkedin.entity.client.EntityClient; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.mxe.MetadataChangeProposal; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; @@ -47,7 +48,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw _entityService ); try { - Domains domains = (Domains) getAspectFromEntity( + Domains domains = (Domains) EntityUtils.getAspectFromEntity( entityUrn.toString(), DOMAINS_ASPECT_NAME, _entityService, diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/domain/UnsetDomainResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/domain/UnsetDomainResolver.java index 82828cbac4477..01dd4f1254f8e 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/domain/UnsetDomainResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/domain/UnsetDomainResolver.java @@ -8,6 +8,7 @@ import com.linkedin.domain.Domains; import com.linkedin.entity.client.EntityClient; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.mxe.MetadataChangeProposal; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; @@ -47,7 +48,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw _entityService ); try { - Domains domains = (Domains) getAspectFromEntity( + Domains domains = (Domains) EntityUtils.getAspectFromEntity( entityUrn.toString(), DOMAINS_ASPECT_NAME, _entityService, diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/embed/UpdateEmbedResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/embed/UpdateEmbedResolver.java index 86b8eb5564152..dbaf6000477aa 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/embed/UpdateEmbedResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/embed/UpdateEmbedResolver.java @@ -10,6 +10,7 @@ import com.linkedin.datahub.graphql.generated.UpdateEmbedInput; import com.linkedin.datahub.graphql.resolvers.mutate.util.EmbedUtils; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.mxe.MetadataChangeProposal; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; @@ -49,7 +50,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw _entityService ); try { - final Embed embed = (Embed) getAspectFromEntity( + final Embed embed = (Embed) EntityUtils.getAspectFromEntity( entityUrn.toString(), EMBED_ASPECT_NAME, _entityService, diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/glossary/AddRelatedTermsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/glossary/AddRelatedTermsResolver.java index 8da8483f7aba8..69b5b14edfbee 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/glossary/AddRelatedTermsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/glossary/AddRelatedTermsResolver.java @@ -12,6 +12,7 @@ import com.linkedin.metadata.Constants; import com.linkedin.metadata.entity.EntityService; import com.linkedin.glossary.GlossaryRelatedTerms; +import com.linkedin.metadata.entity.EntityUtils; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import lombok.RequiredArgsConstructor; @@ -48,7 +49,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw validateRelatedTermsInput(urn, termUrns); Urn actor = Urn.createFromString(((QueryContext) context).getActorUrn()); - GlossaryRelatedTerms glossaryRelatedTerms = (GlossaryRelatedTerms) getAspectFromEntity( + GlossaryRelatedTerms glossaryRelatedTerms = (GlossaryRelatedTerms) EntityUtils.getAspectFromEntity( urn.toString(), Constants.GLOSSARY_RELATED_TERM_ASPECT_NAME, _entityService, diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/glossary/RemoveRelatedTermsResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/glossary/RemoveRelatedTermsResolver.java index 877558295ccdf..417ef4292d0f7 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/glossary/RemoveRelatedTermsResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/glossary/RemoveRelatedTermsResolver.java @@ -11,6 +11,7 @@ import com.linkedin.glossary.GlossaryRelatedTerms; import com.linkedin.metadata.Constants; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import lombok.RequiredArgsConstructor; @@ -21,7 +22,6 @@ import java.util.stream.Collectors; import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument; -import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.getAspectFromEntity; import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.persistAspect; @Slf4j @@ -51,7 +51,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw Urn actor = Urn.createFromString(((QueryContext) context).getActorUrn()); - GlossaryRelatedTerms glossaryRelatedTerms = (GlossaryRelatedTerms) getAspectFromEntity( + GlossaryRelatedTerms glossaryRelatedTerms = (GlossaryRelatedTerms) EntityUtils.getAspectFromEntity( urn.toString(), Constants.GLOSSARY_RELATED_TERM_ASPECT_NAME, _entityService, diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/DescriptionUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/DescriptionUtils.java index 914b0b44acd53..59d5d6939c04c 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/DescriptionUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/DescriptionUtils.java @@ -17,6 +17,7 @@ import com.linkedin.metadata.Constants; import com.linkedin.metadata.authorization.PoliciesConfig; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.ml.metadata.EditableMLFeatureProperties; import com.linkedin.ml.metadata.EditableMLFeatureTableProperties; import com.linkedin.ml.metadata.EditableMLModelGroupProperties; @@ -40,8 +41,6 @@ public class DescriptionUtils { private DescriptionUtils() { } - public static final String EDITABLE_SCHEMA_METADATA = "editableSchemaMetadata"; - public static void updateFieldDescription( String newDescription, Urn resourceUrn, @@ -50,13 +49,13 @@ public static void updateFieldDescription( EntityService entityService ) { EditableSchemaMetadata editableSchemaMetadata = - (EditableSchemaMetadata) getAspectFromEntity( - resourceUrn.toString(), EDITABLE_SCHEMA_METADATA, entityService, new EditableSchemaMetadata()); + (EditableSchemaMetadata) EntityUtils.getAspectFromEntity( + resourceUrn.toString(), Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, entityService, new EditableSchemaMetadata()); EditableSchemaFieldInfo editableFieldInfo = getFieldInfoFromSchema(editableSchemaMetadata, fieldPath); editableFieldInfo.setDescription(newDescription); - persistAspect(resourceUrn, EDITABLE_SCHEMA_METADATA, editableSchemaMetadata, actor, entityService); + persistAspect(resourceUrn, Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, editableSchemaMetadata, actor, entityService); } public static void updateContainerDescription( @@ -66,7 +65,7 @@ public static void updateContainerDescription( EntityService entityService ) { EditableContainerProperties containerProperties = - (EditableContainerProperties) getAspectFromEntity( + (EditableContainerProperties) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.CONTAINER_EDITABLE_PROPERTIES_ASPECT_NAME, entityService, new EditableContainerProperties()); containerProperties.setDescription(newDescription); persistAspect(resourceUrn, Constants.CONTAINER_EDITABLE_PROPERTIES_ASPECT_NAME, containerProperties, actor, entityService); @@ -79,7 +78,7 @@ public static void updateDomainDescription( EntityService entityService ) { DomainProperties domainProperties = - (DomainProperties) getAspectFromEntity( + (DomainProperties) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.DOMAIN_PROPERTIES_ASPECT_NAME, entityService, null); if (domainProperties == null) { // If there are no properties for the domain already, then we should throw since the properties model also requires a name. @@ -96,7 +95,7 @@ public static void updateTagDescription( EntityService entityService ) { TagProperties tagProperties = - (TagProperties) getAspectFromEntity( + (TagProperties) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.TAG_PROPERTIES_ASPECT_NAME, entityService, null); if (tagProperties == null) { // If there are no properties for the tag already, then we should throw since the properties model also requires a name. @@ -113,7 +112,7 @@ public static void updateCorpGroupDescription( EntityService entityService ) { CorpGroupEditableInfo corpGroupEditableInfo = - (CorpGroupEditableInfo) getAspectFromEntity( + (CorpGroupEditableInfo) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.CORP_GROUP_EDITABLE_INFO_ASPECT_NAME, entityService, new CorpGroupEditableInfo()); if (corpGroupEditableInfo != null) { corpGroupEditableInfo.setDescription(newDescription); @@ -127,7 +126,7 @@ public static void updateGlossaryTermDescription( Urn actor, EntityService entityService ) { - GlossaryTermInfo glossaryTermInfo = (GlossaryTermInfo) getAspectFromEntity( + GlossaryTermInfo glossaryTermInfo = (GlossaryTermInfo) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.GLOSSARY_TERM_INFO_ASPECT_NAME, entityService, null); if (glossaryTermInfo == null) { // If there are no properties for the term already, then we should throw since the properties model also requires a name. @@ -143,7 +142,7 @@ public static void updateGlossaryNodeDescription( Urn actor, EntityService entityService ) { - GlossaryNodeInfo glossaryNodeInfo = (GlossaryNodeInfo) getAspectFromEntity( + GlossaryNodeInfo glossaryNodeInfo = (GlossaryNodeInfo) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.GLOSSARY_NODE_INFO_ASPECT_NAME, entityService, null); if (glossaryNodeInfo == null) { throw new IllegalArgumentException("Glossary Node does not exist"); @@ -157,7 +156,7 @@ public static void updateNotebookDescription( Urn resourceUrn, Urn actor, EntityService entityService) { - EditableNotebookProperties notebookProperties = (EditableNotebookProperties) getAspectFromEntity( + EditableNotebookProperties notebookProperties = (EditableNotebookProperties) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.EDITABLE_NOTEBOOK_PROPERTIES_ASPECT_NAME, entityService, null); if (notebookProperties != null) { notebookProperties.setDescription(newDescription); @@ -293,7 +292,7 @@ public static void updateMlModelDescription( Urn resourceUrn, Urn actor, EntityService entityService) { - EditableMLModelProperties editableProperties = (EditableMLModelProperties) getAspectFromEntity( + EditableMLModelProperties editableProperties = (EditableMLModelProperties) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.ML_MODEL_EDITABLE_PROPERTIES_ASPECT_NAME, entityService, new EditableMLModelProperties()); if (editableProperties != null) { editableProperties.setDescription(newDescription); @@ -306,7 +305,7 @@ public static void updateMlModelGroupDescription( Urn resourceUrn, Urn actor, EntityService entityService) { - EditableMLModelGroupProperties editableProperties = (EditableMLModelGroupProperties) getAspectFromEntity( + EditableMLModelGroupProperties editableProperties = (EditableMLModelGroupProperties) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.ML_MODEL_GROUP_EDITABLE_PROPERTIES_ASPECT_NAME, entityService, new EditableMLModelGroupProperties()); if (editableProperties != null) { editableProperties.setDescription(newDescription); @@ -318,7 +317,7 @@ public static void updateMlFeatureDescription( Urn resourceUrn, Urn actor, EntityService entityService) { - EditableMLFeatureProperties editableProperties = (EditableMLFeatureProperties) getAspectFromEntity( + EditableMLFeatureProperties editableProperties = (EditableMLFeatureProperties) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.ML_FEATURE_EDITABLE_PROPERTIES_ASPECT_NAME, entityService, new EditableMLFeatureProperties()); if (editableProperties != null) { editableProperties.setDescription(newDescription); @@ -331,7 +330,7 @@ public static void updateMlFeatureTableDescription( Urn resourceUrn, Urn actor, EntityService entityService) { - EditableMLFeatureTableProperties editableProperties = (EditableMLFeatureTableProperties) getAspectFromEntity( + EditableMLFeatureTableProperties editableProperties = (EditableMLFeatureTableProperties) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.ML_FEATURE_TABLE_EDITABLE_PROPERTIES_ASPECT_NAME, entityService, new EditableMLFeatureTableProperties()); if (editableProperties != null) { editableProperties.setDescription(newDescription); @@ -344,7 +343,7 @@ public static void updateMlPrimaryKeyDescription( Urn resourceUrn, Urn actor, EntityService entityService) { - EditableMLPrimaryKeyProperties editableProperties = (EditableMLPrimaryKeyProperties) getAspectFromEntity( + EditableMLPrimaryKeyProperties editableProperties = (EditableMLPrimaryKeyProperties) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.ML_PRIMARY_KEY_EDITABLE_PROPERTIES_ASPECT_NAME, entityService, new EditableMLPrimaryKeyProperties()); if (editableProperties != null) { editableProperties.setDescription(newDescription); @@ -357,7 +356,7 @@ public static void updateDataProductDescription( Urn resourceUrn, Urn actor, EntityService entityService) { - DataProductProperties properties = (DataProductProperties) getAspectFromEntity( + DataProductProperties properties = (DataProductProperties) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.DATA_PRODUCT_PROPERTIES_ASPECT_NAME, entityService, new DataProductProperties()); if (properties != null) { properties.setDescription(newDescription); diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/MutationUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/MutationUtils.java index 0cf9acd62f736..c862fcfa83594 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/MutationUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/MutationUtils.java @@ -1,12 +1,13 @@ package com.linkedin.datahub.graphql.resolvers.mutate; -import com.linkedin.common.AuditStamp; import com.linkedin.common.urn.Urn; import com.linkedin.data.template.RecordTemplate; import com.linkedin.data.template.StringMap; import com.linkedin.datahub.graphql.generated.SubResourceType; import com.linkedin.events.metadata.ChangeType; +import com.linkedin.metadata.Constants; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.metadata.utils.GenericRecordUtils; import com.linkedin.mxe.MetadataChangeProposal; import com.linkedin.mxe.SystemMetadata; @@ -23,13 +24,12 @@ @Slf4j public class MutationUtils { - public static final String SCHEMA_ASPECT_NAME = "schemaMetadata"; private MutationUtils() { } public static void persistAspect(Urn urn, String aspectName, RecordTemplate aspect, Urn actor, EntityService entityService) { final MetadataChangeProposal proposal = buildMetadataChangeProposalWithUrn(urn, aspectName, aspect); - entityService.ingestProposal(proposal, getAuditStamp(actor), false); + entityService.ingestProposal(proposal, EntityUtils.getAuditStamp(actor), false); } /** @@ -76,38 +76,6 @@ private static MetadataChangeProposal setProposalProperties(MetadataChangePropos return proposal; } - public static RecordTemplate getAspectFromEntity(String entityUrn, String aspectName, EntityService entityService, RecordTemplate defaultValue) { - try { - RecordTemplate aspect = entityService.getAspect( - Urn.createFromString(entityUrn), - aspectName, - 0 - ); - - if (aspect == null) { - return defaultValue; - } - - return aspect; - } catch (Exception e) { - log.error( - "Error constructing aspect from entity. Entity: {} aspect: {}. Error: {}", - entityUrn, - aspectName, - e.toString() - ); - e.printStackTrace(); - return null; - } - } - - public static AuditStamp getAuditStamp(Urn actor) { - AuditStamp auditStamp = new AuditStamp(); - auditStamp.setTime(System.currentTimeMillis()); - auditStamp.setActor(actor); - return auditStamp; - } - public static EditableSchemaFieldInfo getFieldInfoFromSchema( EditableSchemaMetadata editableSchemaMetadata, String fieldPath @@ -139,7 +107,8 @@ public static Boolean validateSubresourceExists( EntityService entityService ) { if (subResourceType.equals(SubResourceType.DATASET_FIELD)) { - SchemaMetadata schemaMetadata = (SchemaMetadata) entityService.getAspect(targetUrn, SCHEMA_ASPECT_NAME, 0); + SchemaMetadata schemaMetadata = (SchemaMetadata) entityService.getAspect(targetUrn, + Constants.SCHEMA_METADATA_ASPECT_NAME, 0); if (schemaMetadata == null) { throw new IllegalArgumentException( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateNameResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateNameResolver.java index b8616363ce4d6..225bee54142c4 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateNameResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateNameResolver.java @@ -18,6 +18,7 @@ import com.linkedin.identity.CorpGroupInfo; import com.linkedin.metadata.Constants; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import lombok.RequiredArgsConstructor; @@ -26,7 +27,6 @@ import java.util.concurrent.CompletableFuture; import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument; -import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.getAspectFromEntity; import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.persistAspect; @Slf4j @@ -73,7 +73,7 @@ private Boolean updateGlossaryTermName( final Urn parentNodeUrn = GlossaryUtils.getParentUrn(targetUrn, context, _entityClient); if (GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn, _entityClient)) { try { - GlossaryTermInfo glossaryTermInfo = (GlossaryTermInfo) getAspectFromEntity( + GlossaryTermInfo glossaryTermInfo = (GlossaryTermInfo) EntityUtils.getAspectFromEntity( targetUrn.toString(), Constants.GLOSSARY_TERM_INFO_ASPECT_NAME, _entityService, null); if (glossaryTermInfo == null) { throw new IllegalArgumentException("Glossary Term does not exist"); @@ -98,7 +98,7 @@ private Boolean updateGlossaryNodeName( final Urn parentNodeUrn = GlossaryUtils.getParentUrn(targetUrn, context, _entityClient); if (GlossaryUtils.canManageChildrenEntities(context, parentNodeUrn, _entityClient)) { try { - GlossaryNodeInfo glossaryNodeInfo = (GlossaryNodeInfo) getAspectFromEntity( + GlossaryNodeInfo glossaryNodeInfo = (GlossaryNodeInfo) EntityUtils.getAspectFromEntity( targetUrn.toString(), Constants.GLOSSARY_NODE_INFO_ASPECT_NAME, _entityService, null); if (glossaryNodeInfo == null) { throw new IllegalArgumentException("Glossary Node does not exist"); @@ -122,7 +122,7 @@ private Boolean updateDomainName( ) { if (AuthorizationUtils.canManageDomains(context)) { try { - DomainProperties domainProperties = (DomainProperties) getAspectFromEntity( + DomainProperties domainProperties = (DomainProperties) EntityUtils.getAspectFromEntity( targetUrn.toString(), Constants.DOMAIN_PROPERTIES_ASPECT_NAME, _entityService, null); if (domainProperties == null) { throw new IllegalArgumentException("Domain does not exist"); @@ -146,7 +146,7 @@ private Boolean updateGroupName( ) { if (AuthorizationUtils.canManageUsersAndGroups(context)) { try { - CorpGroupInfo corpGroupInfo = (CorpGroupInfo) getAspectFromEntity( + CorpGroupInfo corpGroupInfo = (CorpGroupInfo) EntityUtils.getAspectFromEntity( targetUrn.toString(), Constants.CORP_GROUP_INFO_ASPECT_NAME, _entityService, null); if (corpGroupInfo == null) { throw new IllegalArgumentException("Group does not exist"); @@ -169,13 +169,13 @@ private Boolean updateDataProductName( QueryContext context ) { try { - DataProductProperties dataProductProperties = (DataProductProperties) getAspectFromEntity( + DataProductProperties dataProductProperties = (DataProductProperties) EntityUtils.getAspectFromEntity( targetUrn.toString(), Constants.DATA_PRODUCT_PROPERTIES_ASPECT_NAME, _entityService, null); if (dataProductProperties == null) { throw new IllegalArgumentException("Data Product does not exist"); } - Domains dataProductDomains = (Domains) getAspectFromEntity( + Domains dataProductDomains = (Domains) EntityUtils.getAspectFromEntity( targetUrn.toString(), Constants.DOMAINS_ASPECT_NAME, _entityService, null); if (dataProductDomains != null && dataProductDomains.hasDomains() && dataProductDomains.getDomains().size() > 0) { // get first domain since we only allow one domain right now diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateParentNodeResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateParentNodeResolver.java index 44893d74d3eb8..5d78bc38eafe8 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateParentNodeResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateParentNodeResolver.java @@ -12,6 +12,7 @@ import com.linkedin.glossary.GlossaryNodeInfo; import com.linkedin.metadata.Constants; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; import lombok.RequiredArgsConstructor; @@ -20,7 +21,6 @@ import java.util.concurrent.CompletableFuture; import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument; -import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.getAspectFromEntity; import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.persistAspect; @Slf4j @@ -76,7 +76,7 @@ private Boolean updateGlossaryTermParentNode( QueryContext context ) { try { - GlossaryTermInfo glossaryTermInfo = (GlossaryTermInfo) getAspectFromEntity( + GlossaryTermInfo glossaryTermInfo = (GlossaryTermInfo) EntityUtils.getAspectFromEntity( targetUrn.toString(), Constants.GLOSSARY_TERM_INFO_ASPECT_NAME, _entityService, null); if (glossaryTermInfo == null) { // If there is no info aspect for the term already, then we should throw since the model also requires a name. @@ -105,7 +105,7 @@ private Boolean updateGlossaryNodeParentNode( QueryContext context ) { try { - GlossaryNodeInfo glossaryNodeInfo = (GlossaryNodeInfo) getAspectFromEntity( + GlossaryNodeInfo glossaryNodeInfo = (GlossaryNodeInfo) EntityUtils.getAspectFromEntity( targetUrn.toString(), Constants.GLOSSARY_NODE_INFO_ASPECT_NAME, _entityService, null); if (glossaryNodeInfo == null) { throw new IllegalArgumentException("Info for this Glossary Node does not yet exist!"); diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateUserSettingResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateUserSettingResolver.java index 8c1d32c470f44..875bc43e7c100 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateUserSettingResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/UpdateUserSettingResolver.java @@ -9,6 +9,7 @@ import com.linkedin.identity.CorpUserAppearanceSettings; import com.linkedin.identity.CorpUserSettings; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.mxe.MetadataChangeProposal; import graphql.schema.DataFetcher; import graphql.schema.DataFetchingEnvironment; @@ -56,7 +57,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw MetadataChangeProposal proposal = buildMetadataChangeProposalWithUrn(actor, CORP_USER_SETTINGS_ASPECT_NAME, newSettings); - _entityService.ingestProposal(proposal, getAuditStamp(actor), false); + _entityService.ingestProposal(proposal, EntityUtils.getAuditStamp(actor), false); return true; } catch (Exception e) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeleteUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeleteUtils.java index 480357b89bb14..7d4c5bee61e19 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeleteUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeleteUtils.java @@ -12,6 +12,7 @@ import com.linkedin.metadata.Constants; import com.linkedin.metadata.authorization.PoliciesConfig; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.mxe.MetadataChangeProposal; import java.util.ArrayList; import java.util.List; @@ -53,7 +54,7 @@ public static void updateStatusForResources( for (String urnStr : urnStrs) { changes.add(buildSoftDeleteProposal(removed, urnStr, actor, entityService)); } - ingestChangeProposals(changes, entityService, actor); + EntityUtils.ingestChangeProposals(changes, entityService, actor, false); } private static MetadataChangeProposal buildSoftDeleteProposal( @@ -62,7 +63,7 @@ private static MetadataChangeProposal buildSoftDeleteProposal( Urn actor, EntityService entityService ) { - Status status = (Status) getAspectFromEntity( + Status status = (Status) EntityUtils.getAspectFromEntity( urnStr, Constants.STATUS_ASPECT_NAME, entityService, @@ -70,11 +71,4 @@ private static MetadataChangeProposal buildSoftDeleteProposal( status.setRemoved(removed); return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(urnStr), Constants.STATUS_ASPECT_NAME, status); } - - private static void ingestChangeProposals(List changes, EntityService entityService, Urn actor) { - // TODO: Replace this with a batch ingest proposals endpoint. - for (MetadataChangeProposal change : changes) { - entityService.ingestProposal(change, getAuditStamp(actor), false); - } - } } \ No newline at end of file diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeprecationUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeprecationUtils.java index 615d76c650286..bd82bbb8e514f 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeprecationUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DeprecationUtils.java @@ -5,15 +5,16 @@ import com.linkedin.common.Deprecation; import com.linkedin.common.urn.Urn; import com.linkedin.common.urn.UrnUtils; -import com.linkedin.data.template.SetMode; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.authorization.AuthorizationUtils; import com.datahub.authorization.ConjunctivePrivilegeGroup; import com.datahub.authorization.DisjunctivePrivilegeGroup; import com.linkedin.datahub.graphql.generated.ResourceRefInput; +import com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils; import com.linkedin.metadata.Constants; import com.linkedin.metadata.authorization.PoliciesConfig; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.mxe.MetadataChangeProposal; import java.util.ArrayList; import java.util.List; @@ -21,7 +22,7 @@ import javax.annotation.Nullable; import lombok.extern.slf4j.Slf4j; -import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.*; +import static com.linkedin.metadata.aspect.utils.DeprecationUtils.*; @Slf4j @@ -58,7 +59,7 @@ public static void updateDeprecationForResources( for (ResourceRefInput resource : resources) { changes.add(buildUpdateDeprecationProposal(deprecated, note, decommissionTime, resource, actor, entityService)); } - ingestChangeProposals(changes, entityService, actor); + EntityUtils.ingestChangeProposals(changes, entityService, actor, false); } private static MetadataChangeProposal buildUpdateDeprecationProposal( @@ -69,27 +70,19 @@ private static MetadataChangeProposal buildUpdateDeprecationProposal( Urn actor, EntityService entityService ) { - Deprecation deprecation = (Deprecation) getAspectFromEntity( - resource.getResourceUrn(), - Constants.DEPRECATION_ASPECT_NAME, - entityService, - new Deprecation()); - deprecation.setActor(actor); - deprecation.setDeprecated(deprecated); - deprecation.setDecommissionTime(decommissionTime, SetMode.REMOVE_IF_NULL); - if (note != null) { - deprecation.setNote(note); - } else { - // Note is required field in GMS. Set to empty string if not provided. - deprecation.setNote(""); - } - return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), Constants.DEPRECATION_ASPECT_NAME, deprecation); - } - - private static void ingestChangeProposals(List changes, EntityService entityService, Urn actor) { - // TODO: Replace this with a batch ingest proposals endpoint. - for (MetadataChangeProposal change : changes) { - entityService.ingestProposal(change, getAuditStamp(actor), false); - } + String resourceUrn = resource.getResourceUrn(); + Deprecation deprecation = getDeprecation( + entityService, + resourceUrn, + actor, + note, + deprecated, + decommissionTime + ); + return MutationUtils.buildMetadataChangeProposalWithUrn( + UrnUtils.getUrn(resourceUrn), + Constants.DEPRECATION_ASPECT_NAME, + deprecation + ); } } \ No newline at end of file diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DomainUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DomainUtils.java index 213c08454d278..b57160be09d32 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DomainUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/DomainUtils.java @@ -14,6 +14,7 @@ import com.linkedin.metadata.Constants; import com.linkedin.metadata.authorization.PoliciesConfig; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.mxe.MetadataChangeProposal; import java.util.ArrayList; import java.util.List; @@ -57,7 +58,7 @@ public static void setDomainForResources( for (ResourceRefInput resource : resources) { changes.add(buildSetDomainProposal(domainUrn, resource, actor, entityService)); } - ingestChangeProposals(changes, entityService, actor); + EntityUtils.ingestChangeProposals(changes, entityService, actor, false); } private static MetadataChangeProposal buildSetDomainProposal( @@ -66,7 +67,7 @@ private static MetadataChangeProposal buildSetDomainProposal( Urn actor, EntityService entityService ) { - Domains domains = (Domains) getAspectFromEntity( + Domains domains = (Domains) EntityUtils.getAspectFromEntity( resource.getResourceUrn(), Constants.DOMAINS_ASPECT_NAME, entityService, @@ -84,11 +85,4 @@ public static void validateDomain(Urn domainUrn, EntityService entityService) { throw new IllegalArgumentException(String.format("Failed to validate Domain with urn %s. Urn does not exist.", domainUrn)); } } - - private static void ingestChangeProposals(List changes, EntityService entityService, Urn actor) { - // TODO: Replace this with a batch ingest proposals endpoint. - for (MetadataChangeProposal change : changes) { - entityService.ingestProposal(change, getAuditStamp(actor), false); - } - } } \ No newline at end of file diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java index e2dbf1d3f9c99..a93c7d5b333da 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LabelUtils.java @@ -17,9 +17,10 @@ import com.datahub.authorization.DisjunctivePrivilegeGroup; import com.linkedin.datahub.graphql.generated.ResourceRefInput; import com.linkedin.datahub.graphql.generated.SubResourceType; -import com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils; +import com.linkedin.metadata.Constants; import com.linkedin.metadata.authorization.PoliciesConfig; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.mxe.MetadataChangeProposal; import com.linkedin.schema.EditableSchemaFieldInfo; import com.linkedin.schema.EditableSchemaMetadata; @@ -41,10 +42,6 @@ public class LabelUtils { private LabelUtils() { } - public static final String GLOSSARY_TERM_ASPECT_NAME = "glossaryTerms"; - public static final String EDITABLE_SCHEMA_METADATA = "editableSchemaMetadata"; - public static final String TAGS_ASPECT_NAME = "globalTags"; - public static void removeTermFromResource( Urn labelUrn, Urn resourceUrn, @@ -54,23 +51,23 @@ public static void removeTermFromResource( ) { if (subResource == null || subResource.equals("")) { com.linkedin.common.GlossaryTerms terms = - (com.linkedin.common.GlossaryTerms) MutationUtils.getAspectFromEntity( - resourceUrn.toString(), GLOSSARY_TERM_ASPECT_NAME, entityService, new GlossaryTerms()); - terms.setAuditStamp(getAuditStamp(actor)); + (com.linkedin.common.GlossaryTerms) EntityUtils.getAspectFromEntity( + resourceUrn.toString(), Constants.GLOSSARY_TERMS_ASPECT_NAME, entityService, new GlossaryTerms()); + terms.setAuditStamp(EntityUtils.getAuditStamp(actor)); removeTermIfExists(terms, labelUrn); - persistAspect(resourceUrn, GLOSSARY_TERM_ASPECT_NAME, terms, actor, entityService); + persistAspect(resourceUrn, Constants.GLOSSARY_TERMS_ASPECT_NAME, terms, actor, entityService); } else { com.linkedin.schema.EditableSchemaMetadata editableSchemaMetadata = - (com.linkedin.schema.EditableSchemaMetadata) getAspectFromEntity( - resourceUrn.toString(), EDITABLE_SCHEMA_METADATA, entityService, new EditableSchemaMetadata()); + (com.linkedin.schema.EditableSchemaMetadata) EntityUtils.getAspectFromEntity( + resourceUrn.toString(), Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, entityService, new EditableSchemaMetadata()); EditableSchemaFieldInfo editableFieldInfo = getFieldInfoFromSchema(editableSchemaMetadata, subResource); if (!editableFieldInfo.hasGlossaryTerms()) { editableFieldInfo.setGlossaryTerms(new GlossaryTerms()); } removeTermIfExists(editableFieldInfo.getGlossaryTerms(), labelUrn); - persistAspect(resourceUrn, EDITABLE_SCHEMA_METADATA, editableSchemaMetadata, actor, entityService); + persistAspect(resourceUrn, Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, editableSchemaMetadata, actor, entityService); } } @@ -84,7 +81,7 @@ public static void removeTagsFromResources( for (ResourceRefInput resource : resources) { changes.add(buildRemoveTagsProposal(tags, resource, actor, entityService)); } - ingestChangeProposals(changes, entityService, actor); + EntityUtils.ingestChangeProposals(changes, entityService, actor, false); } public static void addTagsToResources( @@ -97,7 +94,7 @@ public static void addTagsToResources( for (ResourceRefInput resource : resources) { changes.add(buildAddTagsProposal(tagUrns, resource, actor, entityService)); } - ingestChangeProposals(changes, entityService, actor); + EntityUtils.ingestChangeProposals(changes, entityService, actor, false); } public static void removeTermsFromResources( @@ -110,7 +107,7 @@ public static void removeTermsFromResources( for (ResourceRefInput resource : resources) { changes.add(buildRemoveTermsProposal(termUrns, resource, actor, entityService)); } - ingestChangeProposals(changes, entityService, actor); + EntityUtils.ingestChangeProposals(changes, entityService, actor, false); } public static void addTermsToResources( @@ -123,7 +120,7 @@ public static void addTermsToResources( for (ResourceRefInput resource : resources) { changes.add(buildAddTermsProposal(termUrns, resource, actor, entityService)); } - ingestChangeProposals(changes, entityService, actor); + EntityUtils.ingestChangeProposals(changes, entityService, actor, false); } public static void addTermsToResource( @@ -135,30 +132,30 @@ public static void addTermsToResource( ) throws URISyntaxException { if (subResource == null || subResource.equals("")) { com.linkedin.common.GlossaryTerms terms = - (com.linkedin.common.GlossaryTerms) getAspectFromEntity(resourceUrn.toString(), GLOSSARY_TERM_ASPECT_NAME, + (com.linkedin.common.GlossaryTerms) EntityUtils.getAspectFromEntity(resourceUrn.toString(), Constants.GLOSSARY_TERMS_ASPECT_NAME, entityService, new GlossaryTerms()); - terms.setAuditStamp(getAuditStamp(actor)); + terms.setAuditStamp(EntityUtils.getAuditStamp(actor)); if (!terms.hasTerms()) { terms.setTerms(new GlossaryTermAssociationArray()); } addTermsIfNotExists(terms, labelUrns); - persistAspect(resourceUrn, GLOSSARY_TERM_ASPECT_NAME, terms, actor, entityService); + persistAspect(resourceUrn, Constants.GLOSSARY_TERMS_ASPECT_NAME, terms, actor, entityService); } else { com.linkedin.schema.EditableSchemaMetadata editableSchemaMetadata = - (com.linkedin.schema.EditableSchemaMetadata) getAspectFromEntity( - resourceUrn.toString(), EDITABLE_SCHEMA_METADATA, entityService, new EditableSchemaMetadata()); + (com.linkedin.schema.EditableSchemaMetadata) EntityUtils.getAspectFromEntity( + resourceUrn.toString(), Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, entityService, new EditableSchemaMetadata()); EditableSchemaFieldInfo editableFieldInfo = getFieldInfoFromSchema(editableSchemaMetadata, subResource); if (!editableFieldInfo.hasGlossaryTerms()) { editableFieldInfo.setGlossaryTerms(new GlossaryTerms()); } - editableFieldInfo.getGlossaryTerms().setAuditStamp(getAuditStamp(actor)); + editableFieldInfo.getGlossaryTerms().setAuditStamp(EntityUtils.getAuditStamp(actor)); addTermsIfNotExists(editableFieldInfo.getGlossaryTerms(), labelUrns); - persistAspect(resourceUrn, EDITABLE_SCHEMA_METADATA, editableSchemaMetadata, actor, entityService); + persistAspect(resourceUrn, Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, editableSchemaMetadata, actor, entityService); } } @@ -321,14 +318,14 @@ private static MetadataChangeProposal buildRemoveTagsToEntityProposal( EntityService entityService ) { com.linkedin.common.GlobalTags tags = - (com.linkedin.common.GlobalTags) getAspectFromEntity(resource.getResourceUrn(), TAGS_ASPECT_NAME, + (com.linkedin.common.GlobalTags) EntityUtils.getAspectFromEntity(resource.getResourceUrn(), Constants.GLOBAL_TAGS_ASPECT_NAME, entityService, new GlobalTags()); if (!tags.hasTags()) { tags.setTags(new TagAssociationArray()); } removeTagsIfExists(tags, tagUrns); - return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), TAGS_ASPECT_NAME, tags); + return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), Constants.GLOBAL_TAGS_ASPECT_NAME, tags); } private static MetadataChangeProposal buildRemoveTagsToSubResourceProposal( @@ -338,9 +335,9 @@ private static MetadataChangeProposal buildRemoveTagsToSubResourceProposal( EntityService entityService ) { com.linkedin.schema.EditableSchemaMetadata editableSchemaMetadata = - (com.linkedin.schema.EditableSchemaMetadata) getAspectFromEntity( + (com.linkedin.schema.EditableSchemaMetadata) EntityUtils.getAspectFromEntity( resource.getResourceUrn(), - EDITABLE_SCHEMA_METADATA, + Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, entityService, new EditableSchemaMetadata()); EditableSchemaFieldInfo editableFieldInfo = getFieldInfoFromSchema(editableSchemaMetadata, resource.getSubResource()); @@ -349,7 +346,8 @@ private static MetadataChangeProposal buildRemoveTagsToSubResourceProposal( editableFieldInfo.setGlobalTags(new GlobalTags()); } removeTagsIfExists(editableFieldInfo.getGlobalTags(), tagUrns); - return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), EDITABLE_SCHEMA_METADATA, editableSchemaMetadata); + return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), + Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, editableSchemaMetadata); } private static MetadataChangeProposal buildAddTagsToEntityProposal( @@ -359,14 +357,14 @@ private static MetadataChangeProposal buildAddTagsToEntityProposal( EntityService entityService ) throws URISyntaxException { com.linkedin.common.GlobalTags tags = - (com.linkedin.common.GlobalTags) getAspectFromEntity(resource.getResourceUrn(), TAGS_ASPECT_NAME, + (com.linkedin.common.GlobalTags) EntityUtils.getAspectFromEntity(resource.getResourceUrn(), Constants.GLOBAL_TAGS_ASPECT_NAME, entityService, new GlobalTags()); if (!tags.hasTags()) { tags.setTags(new TagAssociationArray()); } addTagsIfNotExists(tags, tagUrns); - return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), TAGS_ASPECT_NAME, tags); + return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), Constants.GLOBAL_TAGS_ASPECT_NAME, tags); } private static MetadataChangeProposal buildAddTagsToSubResourceProposal( @@ -376,8 +374,8 @@ private static MetadataChangeProposal buildAddTagsToSubResourceProposal( EntityService entityService ) throws URISyntaxException { com.linkedin.schema.EditableSchemaMetadata editableSchemaMetadata = - (com.linkedin.schema.EditableSchemaMetadata) getAspectFromEntity( - resource.getResourceUrn(), EDITABLE_SCHEMA_METADATA, entityService, new EditableSchemaMetadata()); + (com.linkedin.schema.EditableSchemaMetadata) EntityUtils.getAspectFromEntity( + resource.getResourceUrn(), Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, entityService, new EditableSchemaMetadata()); EditableSchemaFieldInfo editableFieldInfo = getFieldInfoFromSchema(editableSchemaMetadata, resource.getSubResource()); if (!editableFieldInfo.hasGlobalTags()) { @@ -385,7 +383,8 @@ private static MetadataChangeProposal buildAddTagsToSubResourceProposal( } addTagsIfNotExists(editableFieldInfo.getGlobalTags(), tagUrns); - return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), EDITABLE_SCHEMA_METADATA, editableSchemaMetadata); + return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), + Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, editableSchemaMetadata); } private static void addTagsIfNotExists(GlobalTags tags, List tagUrns) throws URISyntaxException { @@ -452,16 +451,16 @@ private static MetadataChangeProposal buildAddTermsToEntityProposal( EntityService entityService ) throws URISyntaxException { com.linkedin.common.GlossaryTerms terms = - (com.linkedin.common.GlossaryTerms) getAspectFromEntity(resource.getResourceUrn(), GLOSSARY_TERM_ASPECT_NAME, + (com.linkedin.common.GlossaryTerms) EntityUtils.getAspectFromEntity(resource.getResourceUrn(), Constants.GLOSSARY_TERMS_ASPECT_NAME, entityService, new GlossaryTerms()); - terms.setAuditStamp(getAuditStamp(actor)); + terms.setAuditStamp(EntityUtils.getAuditStamp(actor)); if (!terms.hasTerms()) { terms.setTerms(new GlossaryTermAssociationArray()); } addTermsIfNotExists(terms, termUrns); - return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), GLOSSARY_TERM_ASPECT_NAME, terms); + return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), Constants.GLOSSARY_TERMS_ASPECT_NAME, terms); } private static MetadataChangeProposal buildAddTermsToSubResourceProposal( @@ -471,18 +470,19 @@ private static MetadataChangeProposal buildAddTermsToSubResourceProposal( EntityService entityService ) throws URISyntaxException { com.linkedin.schema.EditableSchemaMetadata editableSchemaMetadata = - (com.linkedin.schema.EditableSchemaMetadata) getAspectFromEntity( - resource.getResourceUrn(), EDITABLE_SCHEMA_METADATA, entityService, new EditableSchemaMetadata()); + (com.linkedin.schema.EditableSchemaMetadata) EntityUtils.getAspectFromEntity( + resource.getResourceUrn(), Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, entityService, new EditableSchemaMetadata()); EditableSchemaFieldInfo editableFieldInfo = getFieldInfoFromSchema(editableSchemaMetadata, resource.getSubResource()); if (!editableFieldInfo.hasGlossaryTerms()) { editableFieldInfo.setGlossaryTerms(new GlossaryTerms()); } - editableFieldInfo.getGlossaryTerms().setAuditStamp(getAuditStamp(actor)); + editableFieldInfo.getGlossaryTerms().setAuditStamp(EntityUtils.getAuditStamp(actor)); addTermsIfNotExists(editableFieldInfo.getGlossaryTerms(), termUrns); - return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), EDITABLE_SCHEMA_METADATA, editableSchemaMetadata); + return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), + Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, editableSchemaMetadata); } private static MetadataChangeProposal buildRemoveTermsToEntityProposal( @@ -492,12 +492,12 @@ private static MetadataChangeProposal buildRemoveTermsToEntityProposal( EntityService entityService ) { com.linkedin.common.GlossaryTerms terms = - (com.linkedin.common.GlossaryTerms) MutationUtils.getAspectFromEntity( - resource.getResourceUrn(), GLOSSARY_TERM_ASPECT_NAME, entityService, new GlossaryTerms()); - terms.setAuditStamp(getAuditStamp(actor)); + (com.linkedin.common.GlossaryTerms) EntityUtils.getAspectFromEntity( + resource.getResourceUrn(), Constants.GLOSSARY_TERMS_ASPECT_NAME, entityService, new GlossaryTerms()); + terms.setAuditStamp(EntityUtils.getAuditStamp(actor)); removeTermsIfExists(terms, termUrns); - return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), GLOSSARY_TERM_ASPECT_NAME, terms); + return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), Constants.GLOSSARY_TERMS_ASPECT_NAME, terms); } private static MetadataChangeProposal buildRemoveTermsToSubResourceProposal( @@ -507,15 +507,16 @@ private static MetadataChangeProposal buildRemoveTermsToSubResourceProposal( EntityService entityService ) { com.linkedin.schema.EditableSchemaMetadata editableSchemaMetadata = - (com.linkedin.schema.EditableSchemaMetadata) getAspectFromEntity( - resource.getResourceUrn(), EDITABLE_SCHEMA_METADATA, entityService, new EditableSchemaMetadata()); + (com.linkedin.schema.EditableSchemaMetadata) EntityUtils.getAspectFromEntity( + resource.getResourceUrn(), Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, entityService, new EditableSchemaMetadata()); EditableSchemaFieldInfo editableFieldInfo = getFieldInfoFromSchema(editableSchemaMetadata, resource.getSubResource()); if (!editableFieldInfo.hasGlossaryTerms()) { editableFieldInfo.setGlossaryTerms(new GlossaryTerms()); } removeTermsIfExists(editableFieldInfo.getGlossaryTerms(), termUrns); - return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), EDITABLE_SCHEMA_METADATA, editableSchemaMetadata); + return buildMetadataChangeProposalWithUrn(UrnUtils.getUrn(resource.getResourceUrn()), + Constants.EDITABLE_SCHEMA_METADATA_ASPECT_NAME, editableSchemaMetadata); } private static void addTermsIfNotExists(GlossaryTerms terms, List termUrns) @@ -556,11 +557,4 @@ private static GlossaryTermAssociationArray removeTermsIfExists(GlossaryTerms te } return termAssociationArray; } - - private static void ingestChangeProposals(List changes, EntityService entityService, Urn actor) { - // TODO: Replace this with a batch ingest proposals endpoint. - for (MetadataChangeProposal change : changes) { - entityService.ingestProposal(change, getAuditStamp(actor), false); - } - } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LinkUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LinkUtils.java index 9725a818cf4c4..9ec0f9b8e6070 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LinkUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/LinkUtils.java @@ -11,11 +11,12 @@ import com.linkedin.datahub.graphql.authorization.AuthorizationUtils; import com.datahub.authorization.ConjunctivePrivilegeGroup; import com.datahub.authorization.DisjunctivePrivilegeGroup; -import com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils; import com.linkedin.metadata.Constants; import com.linkedin.metadata.authorization.PoliciesConfig; import com.linkedin.metadata.entity.EntityService; import javax.annotation.Nonnull; + +import com.linkedin.metadata.entity.EntityUtils; import lombok.extern.slf4j.Slf4j; import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.*; @@ -36,7 +37,7 @@ public static void addLink( Urn actor, EntityService entityService ) { - InstitutionalMemory institutionalMemoryAspect = (InstitutionalMemory) getAspectFromEntity( + InstitutionalMemory institutionalMemoryAspect = (InstitutionalMemory) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.INSTITUTIONAL_MEMORY_ASPECT_NAME, entityService, @@ -51,7 +52,7 @@ public static void removeLink( Urn actor, EntityService entityService ) { - InstitutionalMemory institutionalMemoryAspect = (InstitutionalMemory) MutationUtils.getAspectFromEntity( + InstitutionalMemory institutionalMemoryAspect = (InstitutionalMemory) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.INSTITUTIONAL_MEMORY_ASPECT_NAME, entityService, @@ -74,7 +75,7 @@ private static void addLink(InstitutionalMemory institutionalMemoryAspect, Strin InstitutionalMemoryMetadata newLink = new InstitutionalMemoryMetadata(); newLink.setUrl(new Url(linkUrl)); - newLink.setCreateStamp(getAuditStamp(actor)); + newLink.setCreateStamp(EntityUtils.getAuditStamp(actor)); newLink.setDescription(linkLabel); // We no longer support, this is really a label. linksArray.add(newLink); } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/OwnerUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/OwnerUtils.java index d8a92fb3f6607..d2f7f896e5953 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/OwnerUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/mutate/util/OwnerUtils.java @@ -17,10 +17,10 @@ import com.linkedin.datahub.graphql.generated.OwnerInput; import com.linkedin.datahub.graphql.generated.OwnershipType; import com.linkedin.datahub.graphql.generated.ResourceRefInput; -import com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils; import com.linkedin.metadata.Constants; import com.linkedin.metadata.authorization.PoliciesConfig; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.mxe.MetadataChangeProposal; import java.util.ArrayList; import java.util.List; @@ -52,7 +52,7 @@ public static void addOwnersToResources( for (ResourceRefInput resource : resources) { changes.add(buildAddOwnersProposal(owners, UrnUtils.getUrn(resource.getResourceUrn()), actor, entityService)); } - ingestChangeProposals(changes, entityService, actor); + EntityUtils.ingestChangeProposals(changes, entityService, actor, false); } public static void removeOwnersFromResources( @@ -65,12 +65,12 @@ public static void removeOwnersFromResources( changes.add(buildRemoveOwnersProposal(ownerUrns, maybeOwnershipTypeUrn, UrnUtils.getUrn(resource.getResourceUrn()), actor, entityService)); } - ingestChangeProposals(changes, entityService, actor); + EntityUtils.ingestChangeProposals(changes, entityService, actor, false); } private static MetadataChangeProposal buildAddOwnersProposal(List owners, Urn resourceUrn, Urn actor, EntityService entityService) { - Ownership ownershipAspect = (Ownership) getAspectFromEntity( + Ownership ownershipAspect = (Ownership) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.OWNERSHIP_ASPECT_NAME, entityService, new Ownership()); @@ -85,12 +85,12 @@ public static MetadataChangeProposal buildRemoveOwnersProposal( Urn actor, EntityService entityService ) { - Ownership ownershipAspect = (Ownership) MutationUtils.getAspectFromEntity( + Ownership ownershipAspect = (Ownership) EntityUtils.getAspectFromEntity( resourceUrn.toString(), Constants.OWNERSHIP_ASPECT_NAME, entityService, new Ownership()); - ownershipAspect.setLastModified(getAuditStamp(actor)); + ownershipAspect.setLastModified(EntityUtils.getAuditStamp(actor)); removeOwnersIfExists(ownershipAspect, ownerUrns, maybeOwnershipTypeUrn); return buildMetadataChangeProposalWithUrn(resourceUrn, Constants.OWNERSHIP_ASPECT_NAME, ownershipAspect); } @@ -268,13 +268,6 @@ public static Boolean validateRemoveInput( return true; } - private static void ingestChangeProposals(List changes, EntityService entityService, Urn actor) { - // TODO: Replace this with a batch ingest proposals endpoint. - for (MetadataChangeProposal change : changes) { - entityService.ingestProposal(change, getAuditStamp(actor), false); - } - } - public static void addCreatorAsOwner( QueryContext context, String urn, diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/AggregateAcrossEntitiesResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/AggregateAcrossEntitiesResolver.java index c30c0ae9bf377..e9140441999e2 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/AggregateAcrossEntitiesResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/AggregateAcrossEntitiesResolver.java @@ -72,6 +72,7 @@ public CompletableFuture get(DataFetchingEnvironment environme 0, 0, // 0 entity count because we don't want resolved entities searchFlags, + null, ResolverUtils.getAuthentication(environment), facets)); } catch (Exception e) { diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/GetQuickFiltersResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/GetQuickFiltersResolver.java index 5f4f8dd974328..17058fd8d7cff 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/GetQuickFiltersResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/GetQuickFiltersResolver.java @@ -90,6 +90,7 @@ private SearchResult getSearchResults(@Nonnull final Authentication authenticati 0, 0, null, + null, authentication); } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchAcrossEntitiesResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchAcrossEntitiesResolver.java index 2fc0f837482d0..1022b25b3cd99 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchAcrossEntitiesResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchAcrossEntitiesResolver.java @@ -9,6 +9,7 @@ import com.linkedin.entity.client.EntityClient; import com.linkedin.metadata.query.SearchFlags; import com.linkedin.metadata.query.filter.Filter; +import com.linkedin.metadata.query.filter.SortCriterion; import com.linkedin.metadata.service.ViewService; import com.linkedin.view.DataHubViewInfo; import graphql.schema.DataFetcher; @@ -58,6 +59,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) final Filter baseFilter = ResolverUtils.buildFilter(input.getFilters(), input.getOrFilters()); SearchFlags searchFlags = mapInputFlags(input.getSearchFlags()); + SortCriterion sortCriterion = input.getSortInput() != null ? mapSortCriterion(input.getSortInput().getSortCriterion()) : null; try { log.debug( @@ -75,6 +77,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) start, count, searchFlags, + sortCriterion, ResolverUtils.getAuthentication(environment))); } catch (Exception e) { log.error( diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchUtils.java index 3b516ce8ca8e6..e40bbca56b416 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchUtils.java @@ -13,6 +13,8 @@ import com.linkedin.metadata.query.filter.Criterion; import com.linkedin.metadata.query.filter.CriterionArray; import com.linkedin.metadata.query.filter.Filter; +import com.linkedin.metadata.query.filter.SortCriterion; +import com.linkedin.metadata.query.filter.SortOrder; import com.linkedin.metadata.service.ViewService; import com.linkedin.view.DataHubViewInfo; import java.util.ArrayList; @@ -373,6 +375,13 @@ public static SearchFlags mapInputFlags(com.linkedin.datahub.graphql.generated.S return searchFlags; } + public static SortCriterion mapSortCriterion(com.linkedin.datahub.graphql.generated.SortCriterion sortCriterion) { + SortCriterion result = new SortCriterion(); + result.setField(sortCriterion.getField()); + result.setOrder(SortOrder.valueOf(sortCriterion.getSortOrder().name())); + return result; + } + public static List getEntityNames(List inputTypes) { final List entityTypes = (inputTypes == null || inputTypes.isEmpty()) ? SEARCHABLE_ENTITY_TYPES : inputTypes; diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/tag/SetTagColorResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/tag/SetTagColorResolver.java index b2781c5463b7f..e2aa5905be8bd 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/tag/SetTagColorResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/tag/SetTagColorResolver.java @@ -11,6 +11,7 @@ import com.linkedin.entity.client.EntityClient; import com.linkedin.metadata.authorization.PoliciesConfig; import com.linkedin.metadata.entity.EntityService; +import com.linkedin.metadata.entity.EntityUtils; import com.linkedin.mxe.MetadataChangeProposal; import com.linkedin.tag.TagProperties; import graphql.schema.DataFetcher; @@ -55,7 +56,7 @@ public CompletableFuture get(DataFetchingEnvironment environment) throw } try { - TagProperties tagProperties = (TagProperties) getAspectFromEntity( + TagProperties tagProperties = (TagProperties) EntityUtils.getAspectFromEntity( tagUrn.toString(), TAG_PROPERTIES_ASPECT_NAME, _entityService, diff --git a/datahub-graphql-core/src/main/resources/search.graphql b/datahub-graphql-core/src/main/resources/search.graphql index 67673aa27ca5a..f15535bfb4eb8 100644 --- a/datahub-graphql-core/src/main/resources/search.graphql +++ b/datahub-graphql-core/src/main/resources/search.graphql @@ -184,6 +184,11 @@ input SearchAcrossEntitiesInput { Flags controlling search options """ searchFlags: SearchFlags + + """ + Optional - Information on how to sort this search result + """ + sortInput: SearchSortInput } """ @@ -1196,3 +1201,36 @@ type BrowseResultGroupV2 { """ hasSubGroups: Boolean! } + +""" +Input required in order to sort search results +""" +input SearchSortInput { + """ + A criterion to sort search results on + """ + sortCriterion: SortCriterion! +} + +""" +Order for sorting +""" +enum SortOrder { + ASCENDING + DESCENDING +} + +""" +A single sorting criterion for sorting search. +""" +input SortCriterion { + """ + A field upon which we'll do sorting on. + """ + field: String! + + """ + The order in which we will be sorting + """ + sortOrder: SortOrder! +} diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/container/ContainerEntitiesResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/container/ContainerEntitiesResolverTest.java index 8573998a92bcf..39a08ca26167d 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/container/ContainerEntitiesResolverTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/container/ContainerEntitiesResolverTest.java @@ -60,6 +60,7 @@ public void testGetSuccess() throws Exception { Mockito.eq(0), Mockito.eq(20), Mockito.eq(null), + Mockito.eq(null), Mockito.any(Authentication.class) )).thenReturn( new SearchResult() diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/domain/DomainEntitiesResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/domain/DomainEntitiesResolverTest.java index daac40495de88..93fe3d0017160 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/domain/DomainEntitiesResolverTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/domain/DomainEntitiesResolverTest.java @@ -63,6 +63,7 @@ public void testGetSuccess() throws Exception { Mockito.eq(0), Mockito.eq(20), Mockito.eq(null), + Mockito.eq(null), Mockito.any(Authentication.class) )).thenReturn( new SearchResult() diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/AggregateAcrossEntitiesResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/AggregateAcrossEntitiesResolverTest.java index 36a34eb3ce338..c161a66d3ee93 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/AggregateAcrossEntitiesResolverTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/AggregateAcrossEntitiesResolverTest.java @@ -360,6 +360,7 @@ public static void testErrorFetchingResults() throws Exception { Mockito.anyInt(), Mockito.anyInt(), Mockito.eq(null), + Mockito.eq(null), Mockito.any(Authentication.class) )).thenThrow(new RemoteInvocationException()); @@ -439,6 +440,7 @@ private static EntityClient initMockEntityClient( Mockito.eq(start), Mockito.eq(limit), Mockito.eq(null), + Mockito.eq(null), Mockito.any(Authentication.class), Mockito.eq(facets) )).thenReturn( @@ -464,6 +466,7 @@ private static void verifyMockEntityClient( Mockito.eq(start), Mockito.eq(limit), Mockito.eq(null), + Mockito.eq(null), Mockito.any(Authentication.class), Mockito.eq(facets) ); diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/GetQuickFiltersResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/GetQuickFiltersResolverTest.java index eb85b86ac742c..a599117c3e165 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/GetQuickFiltersResolverTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/GetQuickFiltersResolverTest.java @@ -110,6 +110,7 @@ public static void testGetQuickFiltersFailure() throws Exception { Mockito.anyInt(), Mockito.anyInt(), Mockito.eq(null), + Mockito.eq(null), Mockito.any(Authentication.class) )).thenThrow(new RemoteInvocationException()); @@ -266,6 +267,7 @@ private static EntityClient initMockEntityClient( Mockito.eq(start), Mockito.eq(limit), Mockito.eq(null), + Mockito.eq(null), Mockito.any(Authentication.class) )).thenReturn( result diff --git a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/SearchAcrossEntitiesResolverTest.java b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/SearchAcrossEntitiesResolverTest.java index 7bf602de9d6a1..b0a681c9b2342 100644 --- a/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/SearchAcrossEntitiesResolverTest.java +++ b/datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/search/SearchAcrossEntitiesResolverTest.java @@ -101,6 +101,7 @@ public static void testApplyViewNullBaseFilter() throws Exception { null, null, TEST_VIEW_URN.toString(), + null, null ); DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class); @@ -195,6 +196,7 @@ public static void testApplyViewBaseFilter() throws Exception { )) ), TEST_VIEW_URN.toString(), + null, null ); DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class); @@ -271,6 +273,7 @@ public static void testApplyViewNullBaseEntityTypes() throws Exception { null, null, TEST_VIEW_URN.toString(), + null, null ); DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class); @@ -347,6 +350,7 @@ public static void testApplyViewEmptyBaseEntityTypes() throws Exception { null, null, TEST_VIEW_URN.toString(), + null, null ); DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class); @@ -405,6 +409,7 @@ public static void testApplyViewViewDoesNotExist() throws Exception { null, null, TEST_VIEW_URN.toString(), + null, null ); DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class); @@ -441,6 +446,7 @@ public static void testApplyViewErrorFetchingView() throws Exception { Mockito.anyInt(), Mockito.anyInt(), Mockito.eq(null), + Mockito.eq(null), Mockito.any(Authentication.class) )).thenThrow(new RemoteInvocationException()); @@ -453,6 +459,7 @@ public static void testApplyViewErrorFetchingView() throws Exception { null, null, TEST_VIEW_URN.toString(), + null, null ); DataFetchingEnvironment mockEnv = Mockito.mock(DataFetchingEnvironment.class); @@ -493,6 +500,7 @@ private static EntityClient initMockEntityClient( Mockito.eq(start), Mockito.eq(limit), Mockito.eq(null), + Mockito.eq(null), Mockito.any(Authentication.class) )).thenReturn( result @@ -516,6 +524,7 @@ private static void verifyMockEntityClient( Mockito.eq(start), Mockito.eq(limit), Mockito.eq(null), + Mockito.eq(null), Mockito.any(Authentication.class) ); } diff --git a/datahub-web-react/src/app/AppProviders.tsx b/datahub-web-react/src/app/AppProviders.tsx index ab08ca33f5fae..1ced44048b502 100644 --- a/datahub-web-react/src/app/AppProviders.tsx +++ b/datahub-web-react/src/app/AppProviders.tsx @@ -3,6 +3,7 @@ import AppConfigProvider from '../AppConfigProvider'; import { EducationStepsProvider } from '../providers/EducationStepsProvider'; import UserContextProvider from './context/UserContextProvider'; import QuickFiltersProvider from '../providers/QuickFiltersProvider'; +import SearchContextProvider from './search/context/SearchContextProvider'; interface Props { children: React.ReactNode; @@ -13,7 +14,9 @@ export default function AppProviders({ children }: Props) { - {children} + + {children} + diff --git a/datahub-web-react/src/app/entity/container/ContainerEntitiesTab.tsx b/datahub-web-react/src/app/entity/container/ContainerEntitiesTab.tsx index 7d921d0c9c37c..96a64688bfd81 100644 --- a/datahub-web-react/src/app/entity/container/ContainerEntitiesTab.tsx +++ b/datahub-web-react/src/app/entity/container/ContainerEntitiesTab.tsx @@ -19,6 +19,7 @@ export const ContainerEntitiesTab = () => { }} emptySearchQuery="*" placeholderText="Filter container entities..." + applyView /> ); }; diff --git a/datahub-web-react/src/app/entity/domain/DomainEntitiesTab.tsx b/datahub-web-react/src/app/entity/domain/DomainEntitiesTab.tsx index 3e0a4700a91ba..9f58df94925ac 100644 --- a/datahub-web-react/src/app/entity/domain/DomainEntitiesTab.tsx +++ b/datahub-web-react/src/app/entity/domain/DomainEntitiesTab.tsx @@ -25,6 +25,7 @@ export const DomainEntitiesTab = () => { emptySearchQuery="*" placeholderText="Filter domain entities..." skipCache + applyView /> ); }; diff --git a/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedEntity.tsx b/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedEntity.tsx index b154b75f05ed7..d0e8de0928b48 100644 --- a/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedEntity.tsx +++ b/datahub-web-react/src/app/entity/glossaryTerm/profile/GlossaryRelatedEntity.tsx @@ -47,6 +47,7 @@ export default function GlossaryRelatedEntity() { emptySearchQuery="*" placeholderText="Filter entities..." skipCache + applyView /> ); } diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearch.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearch.tsx index da031a532e04c..649645532d2f5 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearch.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearch.tsx @@ -26,6 +26,7 @@ import { DownloadSearchResults, } from '../../../../../search/utils/types'; import { useEntityContext } from '../../../EntityContext'; +import { useUserContext } from '../../../../../context/useUserContext'; const Container = styled.div` display: flex; @@ -103,6 +104,7 @@ type Props = { }; shouldRefetch?: boolean; resetShouldRefetch?: () => void; + applyView?: boolean; }; export const EmbeddedListSearch = ({ @@ -130,6 +132,7 @@ export const EmbeddedListSearch = ({ useGetDownloadSearchResults = useDownloadScrollAcrossEntitiesSearchResults, shouldRefetch, resetShouldRefetch, + applyView = false, }: Props) => { const { shouldRefetchEmbeddedListSearch, setShouldRefetchEmbeddedListSearch } = useEntityContext(); // Adjust query based on props @@ -165,12 +168,16 @@ export const EmbeddedListSearch = ({ skip: true, }); + const userContext = useUserContext(); + const selectedViewUrn = userContext.localState?.selectedViewUrn; + let searchInput: SearchAcrossEntitiesInput = { types: entityTypes || [], query: finalQuery, start: (page - 1) * numResultsPerPage, count: numResultsPerPage, orFilters: finalFilters, + viewUrn: applyView ? selectedViewUrn : undefined, }; if (skipCache) { searchInput = { ...searchInput, searchFlags: { skipCache: true } }; @@ -295,6 +302,7 @@ export const EmbeddedListSearch = ({ selectedEntities={selectedEntities} setSelectedEntities={setSelectedEntities} entityAction={entityAction} + applyView={applyView} /> ); diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchModal.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchModal.tsx index 3a5fdbf4fba4e..d80ada885330f 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchModal.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchModal.tsx @@ -30,6 +30,7 @@ type Props = { searchBarStyle?: any; searchBarInputStyle?: any; entityAction?: React.FC; + applyView?: boolean; }; export const EmbeddedListSearchModal = ({ @@ -44,6 +45,7 @@ export const EmbeddedListSearchModal = ({ searchBarStyle, searchBarInputStyle, entityAction, + applyView, }: Props) => { // Component state const [query, setQuery] = useState(''); @@ -93,6 +95,7 @@ export const EmbeddedListSearchModal = ({ searchBarStyle={searchBarStyle} searchBarInputStyle={searchBarInputStyle} entityAction={entityAction} + applyView={applyView} /> diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchResults.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchResults.tsx index 893e66fcf7758..bad7f32db5361 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchResults.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchResults.tsx @@ -8,6 +8,7 @@ import { ReactComponent as LoadingSvg } from '../../../../../../images/datahub-l import { EntityAndType } from '../../../types'; import { UnionType } from '../../../../../search/utils/constants'; import { SearchFiltersSection } from '../../../../../search/SearchFiltersSection'; +import MatchingViewsLabel from './MatchingViewsLabel'; const SearchBody = styled.div` height: 100%; @@ -75,6 +76,7 @@ interface Props { numResultsPerPage: number; setNumResultsPerPage: (numResults: number) => void; entityAction?: React.FC; + applyView?: boolean; } export const EmbeddedListSearchResults = ({ @@ -94,6 +96,7 @@ export const EmbeddedListSearchResults = ({ numResultsPerPage, setNumResultsPerPage, entityAction, + applyView, }: Props) => { const pageStart = searchResponse?.start || 0; const pageSize = searchResponse?.count || 0; @@ -159,7 +162,7 @@ export const EmbeddedListSearchResults = ({ onShowSizeChange={(_currNum, newNum) => setNumResultsPerPage(newNum)} pageSizeOptions={['10', '20', '50', '100']} /> - + {applyView ? : } ); diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchSection.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchSection.tsx index 0e390b04b3c5a..2f688af218d98 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchSection.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchSection.tsx @@ -51,6 +51,7 @@ type Props = { }; shouldRefetch?: boolean; resetShouldRefetch?: () => void; + applyView?: boolean; }; export const EmbeddedListSearchSection = ({ @@ -67,6 +68,7 @@ export const EmbeddedListSearchSection = ({ useGetDownloadSearchResults, shouldRefetch, resetShouldRefetch, + applyView, }: Props) => { const history = useHistory(); const location = useLocation(); @@ -152,6 +154,7 @@ export const EmbeddedListSearchSection = ({ useGetDownloadSearchResults={useGetDownloadSearchResults} shouldRefetch={shouldRefetch} resetShouldRefetch={resetShouldRefetch} + applyView={applyView} /> ); }; diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/MatchingViewsLabel.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/MatchingViewsLabel.tsx new file mode 100644 index 0000000000000..4af0998d022a1 --- /dev/null +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/MatchingViewsLabel.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { Button, Typography } from 'antd'; +import styled from 'styled-components'; +import { ANTD_GRAY } from '../../../constants'; +import { useListGlobalViewsQuery, useListMyViewsQuery } from '../../../../../../graphql/view.generated'; +import { DEFAULT_LIST_VIEWS_PAGE_SIZE } from '../../../../view/utils'; +import { useUserContext } from '../../../../../context/useUserContext'; +import { DataHubViewType } from '../../../../../../types.generated'; + +const MatchingViewsLabel = () => { + const userContext = useUserContext(); + const selectedViewUrn = userContext?.localState?.selectedViewUrn; + + const StyledMatchingViewsLabel = styled.div` + color: ${ANTD_GRAY[8]}; + `; + + /** + * Fetch all personal/private views using listMyViews + */ + const { data: personalViewsData } = useListMyViewsQuery({ + variables: { + start: 0, + count: DEFAULT_LIST_VIEWS_PAGE_SIZE, + viewType: DataHubViewType.Personal, + }, + fetchPolicy: 'cache-first', + }); + + /** + * Fetch all global/public views using listGlobalViews + */ + const { data: globalViewsData } = useListGlobalViewsQuery({ + variables: { + start: 0, + count: DEFAULT_LIST_VIEWS_PAGE_SIZE, + }, + fetchPolicy: 'cache-first', + }); + + const onClear = () => { + userContext.updateLocalState({ + ...userContext.localState, + selectedViewUrn: undefined, + }); + }; + + const personalViews = personalViewsData?.listMyViews?.views || []; + const globalViews = globalViewsData?.listGlobalViews?.views || []; + + /** + * Check if selectedViewUrn exists in either the user's private or public views and if so use it + */ + const selectedView = selectedViewUrn + ? personalViews?.find((view) => view.urn === selectedViewUrn) || + globalViews?.find((view) => view.urn === selectedViewUrn) + : undefined; + + return ( + <> + {selectedView ? ( + + Only showing entities in the + {selectedView?.name} + view. + + + ) : ( +
+ )} + + ); +}; + +export default MatchingViewsLabel; diff --git a/datahub-web-react/src/app/entity/shared/constants.ts b/datahub-web-react/src/app/entity/shared/constants.ts index bc40ba871e7d1..e14affc95b6f9 100644 --- a/datahub-web-react/src/app/entity/shared/constants.ts +++ b/datahub-web-react/src/app/entity/shared/constants.ts @@ -20,6 +20,13 @@ export const ANTD_GRAY = { 9: '#434343', }; +export const ANTD_GRAY_V2 = { + 2: '#F3F5F6', + 5: '#DDE0E4', + 8: '#5E666E', + 10: '#1B1E22', +}; + export const EMPTY_MESSAGES = { documentation: { title: 'No documentation yet', diff --git a/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/AboutSection/DescriptionSection.tsx b/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/AboutSection/DescriptionSection.tsx index 1278b8ef09aed..a9c406306880d 100644 --- a/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/AboutSection/DescriptionSection.tsx +++ b/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/AboutSection/DescriptionSection.tsx @@ -19,18 +19,20 @@ const ContentWrapper = styled.div` interface Props { description: string; + isExpandable?: boolean; + limit?: number; } -export default function DescriptionSection({ description }: Props) { +export default function DescriptionSection({ description, isExpandable, limit }: Props) { const isOverLimit = description && removeMarkdown(description).length > ABBREVIATED_LIMIT; const [isExpanded, setIsExpanded] = useState(!isOverLimit); const routeToTab = useRouteToTab(); const isCompact = React.useContext(CompactContext); - const shouldShowReadMore = !useIsOnTab('Documentation'); + const shouldShowReadMore = !useIsOnTab('Documentation') || isExpandable; // if we're not in compact mode, route them to the Docs tab for the best documentation viewing experience function readMore() { - if (isCompact) { + if (isCompact || isExpandable) { setIsExpanded(true); } else { routeToTab({ tabName: 'Documentation' }); @@ -47,7 +49,7 @@ export default function DescriptionSection({ description }: Props) { )} {!isExpanded && ( Read More : undefined } diff --git a/datahub-web-react/src/app/entity/shared/containers/profile/utils.ts b/datahub-web-react/src/app/entity/shared/containers/profile/utils.ts index fabf0b2c51e95..d7f113d85aa76 100644 --- a/datahub-web-react/src/app/entity/shared/containers/profile/utils.ts +++ b/datahub-web-react/src/app/entity/shared/containers/profile/utils.ts @@ -60,7 +60,7 @@ export function getDataForEntityType({ }; } - if (anyEntityData?.siblings?.siblings?.length > 0 && !isHideSiblingMode) { + if (anyEntityData?.siblings?.siblings?.filter((sibling) => sibling.exists).length > 0 && !isHideSiblingMode) { const genericSiblingProperties: GenericEntityProperties[] = anyEntityData?.siblings?.siblings?.map((sibling) => getDataForEntityType({ data: sibling, getOverrideProperties: () => ({}) }), ); diff --git a/datahub-web-react/src/app/entity/shared/tabs/Documentation/components/DescriptionEditor.tsx b/datahub-web-react/src/app/entity/shared/tabs/Documentation/components/DescriptionEditor.tsx index a0faa348d35a1..49bf682944f34 100644 --- a/datahub-web-react/src/app/entity/shared/tabs/Documentation/components/DescriptionEditor.tsx +++ b/datahub-web-react/src/app/entity/shared/tabs/Documentation/components/DescriptionEditor.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { message } from 'antd'; -import styled from 'styled-components'; +import styled from 'styled-components/macro'; import analytics, { EventType, EntityActionType } from '../../../../../analytics'; import { GenericEntityUpdate } from '../../../types'; import { useEntityData, useEntityUpdate, useMutationUrn, useRefetch } from '../../../EntityContext'; @@ -9,10 +9,17 @@ import { DiscardDescriptionModal } from './DiscardDescriptionModal'; import { EDITED_DESCRIPTIONS_CACHE_NAME } from '../../../utils'; import { DescriptionEditorToolbar } from './DescriptionEditorToolbar'; import { Editor } from './editor/Editor'; +import SourceDescription from './SourceDesription'; const EditorContainer = styled.div` + flex: 1; +`; + +const EditorSourceWrapper = styled.div` overflow: auto; - height: 100%; + display: flex; + flex-direction: column; + flex: 1; `; type DescriptionEditorProps = { @@ -138,9 +145,12 @@ export const DescriptionEditor = ({ onComplete }: DescriptionEditorProps) => { onClose={handleConfirmClose} disableSave={!isDescriptionUpdated} /> - - - + + + + + + {confirmCloseModalVisible && ( + {platformName ? <span>{platformName}</span> : <>Source</>} Documentation: + + + ); +} diff --git a/datahub-web-react/src/app/entity/shared/tabs/Documentation/components/editor/OnChangeMarkdown.tsx b/datahub-web-react/src/app/entity/shared/tabs/Documentation/components/editor/OnChangeMarkdown.tsx index 0c4fe8cd97f22..b6a090f892440 100644 --- a/datahub-web-react/src/app/entity/shared/tabs/Documentation/components/editor/OnChangeMarkdown.tsx +++ b/datahub-web-react/src/app/entity/shared/tabs/Documentation/components/editor/OnChangeMarkdown.tsx @@ -11,7 +11,8 @@ export const OnChangeMarkdown = ({ onChange }: OnChangeMarkdownProps): null => { const onDocChanged = useCallback( ({ state }) => { - const markdown = getMarkdown(state); + let markdown = getMarkdown(state); + if (markdown === ' ') markdown = ''; onChange(markdown); }, [onChange, getMarkdown], diff --git a/datahub-web-react/src/app/entity/view/menu/ViewDropdownMenu.tsx b/datahub-web-react/src/app/entity/view/menu/ViewDropdownMenu.tsx index 1c05f82ed0804..1c3183d87e7d9 100644 --- a/datahub-web-react/src/app/entity/view/menu/ViewDropdownMenu.tsx +++ b/datahub-web-react/src/app/entity/view/menu/ViewDropdownMenu.tsx @@ -33,6 +33,17 @@ const MenuButton = styled(MoreOutlined)` } `; +const MenuStyled = styled(Menu)` + &&& { + .ant-dropdown-menu-item:not(:hover) { + background: none; + } + .ant-dropdown-menu-item:hover { + background: #f5f5f5; + } + } +`; + const DEFAULT_VIEW_BUILDER_STATE = { mode: ViewBuilderMode.EDITOR, visible: false, @@ -233,7 +244,7 @@ export const ViewDropdownMenu = ({ <> + {(canManageView && ) || ( )} @@ -247,7 +258,7 @@ export const ViewDropdownMenu = ({ setGlobalDefault(view.urn)} /> )} {canManageView && } - + } trigger={[trigger]} > diff --git a/datahub-web-react/src/app/entity/view/select/ViewOptionName.tsx b/datahub-web-react/src/app/entity/view/select/ViewOptionName.tsx index ff89b0005912f..718e3d136d829 100644 --- a/datahub-web-react/src/app/entity/view/select/ViewOptionName.tsx +++ b/datahub-web-react/src/app/entity/view/select/ViewOptionName.tsx @@ -17,7 +17,7 @@ type Props = { export const ViewOptionName = ({ name, description }: Props) => { return ( - }> + }> {name} ); diff --git a/datahub-web-react/src/app/entity/view/select/ViewSelect.tsx b/datahub-web-react/src/app/entity/view/select/ViewSelect.tsx index d5d779aabe2f1..03689460eb02b 100644 --- a/datahub-web-react/src/app/entity/view/select/ViewSelect.tsx +++ b/datahub-web-react/src/app/entity/view/select/ViewSelect.tsx @@ -1,24 +1,18 @@ import React, { useEffect, useRef, useState } from 'react'; import { useHistory } from 'react-router'; import { Select } from 'antd'; +import styled from 'styled-components'; +import { VscTriangleDown } from 'react-icons/vsc'; import { useListMyViewsQuery, useListGlobalViewsQuery } from '../../../../graphql/view.generated'; import { useUserContext } from '../../../context/useUserContext'; import { DataHubView, DataHubViewType } from '../../../../types.generated'; import { ViewBuilder } from '../builder/ViewBuilder'; import { DEFAULT_LIST_VIEWS_PAGE_SIZE } from '../utils'; import { PageRoutes } from '../../../../conf/Global'; -import { ViewSelectToolTip } from './ViewSelectToolTip'; import { ViewBuilderMode } from '../builder/types'; import { ViewSelectDropdown } from './ViewSelectDropdown'; import { renderViewOptionGroup } from './renderViewOptionGroup'; - -const selectStyle = { - width: 240, -}; - -const dropdownStyle = { - position: 'fixed', -} as any; +import { ANTD_GRAY_V2 } from '../../shared/constants'; type ViewBuilderDisplayState = { mode: ViewBuilderMode; @@ -26,12 +20,46 @@ type ViewBuilderDisplayState = { view?: DataHubView; }; +const TriangleIcon = styled(VscTriangleDown)<{ isOpen: boolean }>` + color: ${(props) => (props.isOpen ? props.theme.styles['primary-color'] : ANTD_GRAY_V2[10])}; +`; + const DEFAULT_VIEW_BUILDER_DISPLAY_STATE = { mode: ViewBuilderMode.EDITOR, visible: false, view: undefined, }; +const ViewSelectContainer = styled.div` + &&& { + display: flex; + align-items: center; + + .ant-select { + .ant-select-selection-search { + position: absolute; + } + &.ant-select-open { + .ant-select-selection-placeholder, + .ant-select-selection-item { + color: ${(props) => props.theme.styles['primary-color']}; + } + } + &:not(.ant-select-open) { + .ant-select-selection-placeholder, + .ant-select-selection-item { + color: ${ANTD_GRAY_V2[10]}; + } + } + .ant-select-selection-placeholder, + .ant-select-selection-item { + font-weight: 700; + font-size: 14px; + } + } + } +`; + /** * The View Select component allows you to select a View to apply to query on the current page. For example, * search, recommendations, and browse. @@ -44,6 +72,7 @@ const DEFAULT_VIEW_BUILDER_DISPLAY_STATE = { export const ViewSelect = () => { const history = useHistory(); const userContext = useUserContext(); + const [isOpen, setIsOpen] = useState(false); const [viewBuilderDisplayState, setViewBuilderDisplayState] = useState( DEFAULT_VIEW_BUILDER_DISPLAY_STATE, ); @@ -153,54 +182,62 @@ export const ViewSelect = () => { (publicViews.filter((view) => view.urn === selectedUrn)?.length || 0) > 0 || false; + const handleDropdownVisibleChange = (isNowOpen: boolean) => { + setIsOpen(isNowOpen); + }; + return ( - <> - - - + + {viewBuilderDisplayState.visible && ( { onCancel={onCloseViewBuilder} /> )} - + ); }; diff --git a/datahub-web-react/src/app/entity/view/select/ViewSelectDropdown.tsx b/datahub-web-react/src/app/entity/view/select/ViewSelectDropdown.tsx index 60a229d8d97a1..17e73d3b5a5b2 100644 --- a/datahub-web-react/src/app/entity/view/select/ViewSelectDropdown.tsx +++ b/datahub-web-react/src/app/entity/view/select/ViewSelectDropdown.tsx @@ -13,9 +13,13 @@ type Props = { export const ViewSelectDropdown = ({ menu, hasViews, onClickCreateView, onClickManageViews, onClickClear }: Props) => { return ( <> - + {hasViews && menu} - + ); }; diff --git a/datahub-web-react/src/app/entity/view/select/ViewSelectFooter.tsx b/datahub-web-react/src/app/entity/view/select/ViewSelectFooter.tsx index c30511d80d52f..2374a0ec78fff 100644 --- a/datahub-web-react/src/app/entity/view/select/ViewSelectFooter.tsx +++ b/datahub-web-react/src/app/entity/view/select/ViewSelectFooter.tsx @@ -1,33 +1,47 @@ import React, { useRef } from 'react'; import styled from 'styled-components'; -import { Button } from 'antd'; -import { RightOutlined } from '@ant-design/icons'; -import { ANTD_GRAY } from '../../shared/constants'; +import { Button, Typography } from 'antd'; +import { PlusOutlined } from '@ant-design/icons'; +import { ANTD_GRAY, ANTD_GRAY_V2 } from '../../shared/constants'; +import { NoMarginButton } from './styledComponents'; const ButtonContainer = styled.div` display: flex; color: ${ANTD_GRAY[6]}; - width: 100%; - justify-content: left; + flex-direction: column; `; -const StyledRightOutlined = styled(RightOutlined)` - && { - font-size: 8px; - color: ${ANTD_GRAY[7]}; +const CreateViewButton = styled(NoMarginButton)<{ bordered: boolean }>` + &&& { + text-align: left; + font-weight: normal; + border-top-left-radius: 0; + border-top-right-radius: 0; + margin-left: 8px; + margin-right: 8px; + padding-left: 0px; + + ${(props) => props.bordered && `border-top: 1px solid ${ANTD_GRAY_V2[5]};`} } `; const ManageViewsButton = styled(Button)` - font-weight: normal; - color: ${ANTD_GRAY[7]}; + &&& { + color: ${(props) => props.theme.styles['primary-color']}; + border-top: 1px solid ${ANTD_GRAY_V2[5]}; + background-color: ${ANTD_GRAY_V2[2]}; + border-top-left-radius: 0; + border-top-right-radius: 0; + } `; type Props = { + hasViews: boolean; + onClickCreateView: () => void; onClickManageViews: () => void; }; -export const ViewSelectFooter = ({ onClickManageViews }: Props) => { +export const ViewSelectFooter = ({ hasViews, onClickCreateView, onClickManageViews }: Props) => { const manageViewsButtonRef = useRef(null); const onHandleClickManageViews = () => { @@ -37,9 +51,17 @@ export const ViewSelectFooter = ({ onClickManageViews }: Props) => { return ( + + + Create new view + Manage Views - ); diff --git a/datahub-web-react/src/app/entity/view/select/ViewSelectHeader.tsx b/datahub-web-react/src/app/entity/view/select/ViewSelectHeader.tsx index 75cca161c4ae4..fcd0434bb4d02 100644 --- a/datahub-web-react/src/app/entity/view/select/ViewSelectHeader.tsx +++ b/datahub-web-react/src/app/entity/view/select/ViewSelectHeader.tsx @@ -1,25 +1,32 @@ import React, { useRef } from 'react'; import styled from 'styled-components'; -import { Button, Typography } from 'antd'; -import { PlusOutlined } from '@ant-design/icons'; +import { NoMarginButton } from './styledComponents'; +import { ANTD_GRAY_V2 } from '../../shared/constants'; const ButtonContainer = styled.div` display: flex; justify-content: space-between; `; -const NoMarginButton = styled(Button)` - && { - margin: 0px; +const AllEntitiesButton = styled(NoMarginButton)` + &&& { + font-weight: normal; + border-bottom: 1px solid ${ANTD_GRAY_V2[5]}; + width: 100%; + text-align: left; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + margin-left: 8px; + margin-right: 8px; + padding-left: 0px; } `; type Props = { - onClickCreateView: () => void; onClickClear: () => void; }; -export const ViewSelectHeader = ({ onClickCreateView, onClickClear }: Props) => { +export const ViewSelectHeader = ({ onClickClear }: Props) => { const clearButtonRef = useRef(null); const onHandleClickClear = () => { @@ -29,18 +36,14 @@ export const ViewSelectHeader = ({ onClickCreateView, onClickClear }: Props) => return ( - - - Create View - - - Clear - + All Entities + ); }; diff --git a/datahub-web-react/src/app/entity/view/select/ViewSelectToolTip.tsx b/datahub-web-react/src/app/entity/view/select/ViewSelectToolTip.tsx deleted file mode 100644 index 5e04250cc21b8..0000000000000 --- a/datahub-web-react/src/app/entity/view/select/ViewSelectToolTip.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import { Tooltip } from 'antd'; - -const HeaderText = styled.div` - margin-bottom: 8px; -`; - -type Props = { - children: React.ReactNode; - visible?: boolean; -}; - -export const ViewSelectToolTip = ({ children, visible = true }: Props) => { - return ( - - Select a View to apply to search results. -
Views help narrow down search results to those that matter most to you.
- - } - > - {children} -
- ); -}; diff --git a/datahub-web-react/src/app/entity/view/select/styledComponents.tsx b/datahub-web-react/src/app/entity/view/select/styledComponents.tsx new file mode 100644 index 0000000000000..30501487bd92e --- /dev/null +++ b/datahub-web-react/src/app/entity/view/select/styledComponents.tsx @@ -0,0 +1,17 @@ +import { RightOutlined } from '@ant-design/icons'; +import { Button } from 'antd'; +import styled from 'styled-components'; +import { ANTD_GRAY } from '../../shared/constants'; + +export const NoMarginButton = styled(Button)` + && { + margin: 0px; + } +`; + +export const StyledRightOutlined = styled(RightOutlined)` + && { + font-size: 8px; + color: ${ANTD_GRAY[7]}; + } +`; diff --git a/datahub-web-react/src/app/home/HomePageHeader.tsx b/datahub-web-react/src/app/home/HomePageHeader.tsx index 02c847bb040a5..def413e13213f 100644 --- a/datahub-web-react/src/app/home/HomePageHeader.tsx +++ b/datahub-web-react/src/app/home/HomePageHeader.tsx @@ -152,6 +152,7 @@ export const HomePageHeader = () => { const showAcrylInfo = useIsShowAcrylInfoEnabled(); const { user } = userContext; const viewUrn = userContext.localState?.selectedViewUrn; + const viewsEnabled = appConfig.config?.viewsConfig?.enabled || false; useEffect(() => { if (suggestionsData !== undefined) { @@ -271,6 +272,7 @@ export const HomePageHeader = () => { onQueryChange={onAutoComplete} autoCompleteStyle={styles.searchBox} entityRegistry={entityRegistry} + viewsEnabled={viewsEnabled} showQuickFilters /> {searchResultsToShow && searchResultsToShow.length > 0 && ( diff --git a/datahub-web-react/src/app/permissions/policy/PolicyActorForm.tsx b/datahub-web-react/src/app/permissions/policy/PolicyActorForm.tsx index 31b9472a7e53b..adb660d43f471 100644 --- a/datahub-web-react/src/app/permissions/policy/PolicyActorForm.tsx +++ b/datahub-web-react/src/app/permissions/policy/PolicyActorForm.tsx @@ -285,6 +285,7 @@ export default function PolicyActorForm({ policyType, actors, setActors }: Props users. Privileges}> Select a set of privileges to permit. updatePolicyName(event.target.value)} /> Type}> The type of policy you would like to create. - setPolicyType(value as PolicyType)} + > + + Platform + + + Metadata + The Platform policy type allows you to assign top-level DataHub Platform privileges to users. @@ -70,6 +79,7 @@ export default function PolicyTypeForm({ An optional description for your new policy. setPolicyDescription(event.target.value)} /> diff --git a/datahub-web-react/src/app/search/SearchBar.tsx b/datahub-web-react/src/app/search/SearchBar.tsx index b29d63ac2d8d9..97be6ab6b65e3 100644 --- a/datahub-web-react/src/app/search/SearchBar.tsx +++ b/datahub-web-react/src/app/search/SearchBar.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState, useRef, useCallback } from 'react'; +import React, { useEffect, useMemo, useState, useRef, useCallback, EventHandler, SyntheticEvent } from 'react'; import { Input, AutoComplete, Button } from 'antd'; import { CloseCircleFilled, SearchOutlined } from '@ant-design/icons'; import styled from 'styled-components/macro'; @@ -6,7 +6,7 @@ import { useHistory } from 'react-router'; import { AutoCompleteResultForEntity, Entity, EntityType, FacetFilterInput, ScenarioType } from '../../types.generated'; import EntityRegistry from '../entity/EntityRegistry'; import filterSearchQuery from './utils/filterSearchQuery'; -import { ANTD_GRAY } from '../entity/shared/constants'; +import { ANTD_GRAY, ANTD_GRAY_V2 } from '../entity/shared/constants'; import { getEntityPath } from '../entity/shared/containers/profile/utils'; import { EXACT_SEARCH_PREFIX } from './utils/constants'; import { useListRecommendationsQuery } from '../../graphql/recommendations.generated'; @@ -22,6 +22,7 @@ import { useUserContext } from '../context/useUserContext'; import { navigateToSearchUrl } from './utils/navigateToSearchUrl'; import { getQuickFilterDetails } from './autoComplete/quickFilters/utils'; import ViewAllSearchItem from './ViewAllSearchItem'; +import { ViewSelect } from '../entity/view/select/ViewSelect'; const StyledAutoComplete = styled(AutoComplete)` width: 100%; @@ -39,9 +40,14 @@ const StyledSearchBar = styled(Input)` height: 40px; font-size: 20px; color: ${ANTD_GRAY[7]}; + background-color: ${ANTD_GRAY_V2[2]}; } > .ant-input { font-size: 14px; + background-color: ${ANTD_GRAY_V2[2]}; + } + > .ant-input::placeholder { + color: ${ANTD_GRAY_V2[10]}; } .ant-input-clear-icon { height: 15px; @@ -56,8 +62,18 @@ const ClearIcon = styled(CloseCircleFilled)` } `; +const ViewSelectContainer = styled.div` + &&& { + border-right: 1px solid ${ANTD_GRAY_V2[5]}; + } +`; + +const SearchIcon = styled(SearchOutlined)` + color: ${ANTD_GRAY_V2[8]}; +`; + const EXACT_AUTOCOMPLETE_OPTION_TYPE = 'exact_query'; -const RECOMMENDED_QUERY_OPTION_TYPE = 'recommendation'; +const RELEVANCE_QUERY_OPTION_TYPE = 'recommendation'; const QUICK_FILTER_AUTO_COMPLETE_OPTION = { label: Filter by, @@ -85,10 +101,14 @@ const renderRecommendedQuery = (query: string) => { return { value: query, label: , - type: RECOMMENDED_QUERY_OPTION_TYPE, + type: RELEVANCE_QUERY_OPTION_TYPE, }; }; +const handleStopPropagation: EventHandler = (e) => { + e.stopPropagation(); +}; + interface Props { initialQuery?: string; placeholderText: string; @@ -102,6 +122,7 @@ interface Props { fixAutoComplete?: boolean; hideRecommendations?: boolean; showQuickFilters?: boolean; + viewsEnabled?: boolean; setIsSearchBarFocused?: (isSearchBarFocused: boolean) => void; onFocus?: () => void; onBlur?: () => void; @@ -127,6 +148,7 @@ export const SearchBar = ({ fixAutoComplete, hideRecommendations, showQuickFilters, + viewsEnabled = false, setIsSearchBarFocused, onFocus, onBlur, @@ -278,10 +300,7 @@ export const SearchBar = ({ filterOption={false} onSelect={(value, option) => { // If the autocomplete option type is NOT an entity, then render as a normal search query. - if ( - option.type === EXACT_AUTOCOMPLETE_OPTION_TYPE || - option.type === RECOMMENDED_QUERY_OPTION_TYPE - ) { + if (option.type === EXACT_AUTOCOMPLETE_OPTION_TYPE || option.type === RELEVANCE_QUERY_OPTION_TYPE) { handleSearch( `${filterSearchQuery(value as string)}`, searchEntityTypes.indexOf(option.type) >= 0 ? option.type : undefined, @@ -326,6 +345,7 @@ export const SearchBar = ({ listHeight={480} > { handleSearch( @@ -334,7 +354,7 @@ export const SearchBar = ({ getFiltersWithQuickFilter(selectedQuickFilter), ); }} - style={inputStyle} + style={{ ...inputStyle, color: 'red' }} value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} data-testid="search-input" @@ -342,15 +362,28 @@ export const SearchBar = ({ onBlur={handleBlur} allowClear={{ clearIcon: }} prefix={ - { - handleSearch( - filterSearchQuery(searchQuery || ''), - undefined, - getFiltersWithQuickFilter(selectedQuickFilter), - ); - }} - /> + <> + {viewsEnabled && ( + + + + )} + { + handleSearch( + filterSearchQuery(searchQuery || ''), + undefined, + getFiltersWithQuickFilter(selectedQuickFilter), + ); + }} + /> + } /> diff --git a/datahub-web-react/src/app/search/SearchHeader.tsx b/datahub-web-react/src/app/search/SearchHeader.tsx index 496b320cd3e3c..1cdd82c8216e1 100644 --- a/datahub-web-react/src/app/search/SearchHeader.tsx +++ b/datahub-web-react/src/app/search/SearchHeader.tsx @@ -11,7 +11,6 @@ import { ANTD_GRAY } from '../entity/shared/constants'; import { HeaderLinks } from '../shared/admin/HeaderLinks'; import { useAppConfig, useIsShowAcrylInfoEnabled } from '../useAppConfig'; import { DEFAULT_APP_CONFIG } from '../../appConfigContext'; -import { ViewSelect } from '../entity/view/select/ViewSelect'; import DemoButton from '../entity/shared/components/styled/DemoButton'; const { Header } = Layout; @@ -49,10 +48,6 @@ const NavGroup = styled.div` min-width: 200px; `; -const ViewSelectContainer = styled.span` - margin-right: 14px; -`; - type Props = { initialQuery: string; placeholderText: string; @@ -85,7 +80,7 @@ export const SearchHeader = ({ const themeConfig = useTheme(); const showAcrylInfo = useIsShowAcrylInfoEnabled(); const appConfig = useAppConfig(); - const viewsEnabled = appConfig.config?.viewsConfig?.enabled; + const viewsEnabled = appConfig.config?.viewsConfig?.enabled || false; return (
@@ -108,16 +103,12 @@ export const SearchHeader = ({ onQueryChange={onQueryChange} entityRegistry={entityRegistry} setIsSearchBarFocused={setIsSearchBarFocused} + viewsEnabled={viewsEnabled} fixAutoComplete showQuickFilters /> - {viewsEnabled && ( - - - - )} {showAcrylInfo && } diff --git a/datahub-web-react/src/app/search/SearchPage.tsx b/datahub-web-react/src/app/search/SearchPage.tsx index 13a11bd1bd8dd..ce353640d8179 100644 --- a/datahub-web-react/src/app/search/SearchPage.tsx +++ b/datahub-web-react/src/app/search/SearchPage.tsx @@ -24,6 +24,7 @@ import useSearchFilterAnalytics from './filters/useSearchFilterAnalytics'; import { useIsBrowseV2, useIsSearchV2, useSearchVersion } from './useSearchAndBrowseVersion'; import useFilterMode from './filters/useFilterMode'; import { useUpdateEducationStepIdsAllowlist } from '../onboarding/useUpdateEducationStepIdsAllowlist'; +import { useSelectedSortOption } from './context/SearchContext'; /** * A search results page. @@ -34,8 +35,9 @@ export const SearchPage = () => { const showBrowseV2 = useIsBrowseV2(); const searchVersion = useSearchVersion(); const history = useHistory(); - const { query, unionType, filters, orFilters, viewUrn, page, activeType } = useGetSearchQueryInputs(); + const { query, unionType, filters, orFilters, viewUrn, page, activeType, sortInput } = useGetSearchQueryInputs(); const { filterMode, filterModeRef, setFilterMode } = useFilterMode(filters, unionType); + const selectedSortOption = useSelectedSortOption(); const [numResultsPerPage, setNumResultsPerPage] = useState(SearchCfg.RESULTS_PER_PAGE); const [isSelectMode, setIsSelectMode] = useState(false); @@ -56,6 +58,7 @@ export const SearchPage = () => { filters: [], orFilters, viewUrn, + sortInput, }, }, }); @@ -93,7 +96,15 @@ export const SearchPage = () => { }; const onChangeFilters = (newFilters: Array) => { - navigateToSearchUrl({ type: activeType, query, page: 1, filters: newFilters, history, unionType }); + navigateToSearchUrl({ + type: activeType, + query, + selectedSortOption, + page: 1, + filters: newFilters, + history, + unionType, + }); }; const onClearFilters = () => { @@ -102,12 +113,28 @@ export const SearchPage = () => { }; const onChangeUnionType = (newUnionType: UnionType) => { - navigateToSearchUrl({ type: activeType, query, page: 1, filters, history, unionType: newUnionType }); + navigateToSearchUrl({ + type: activeType, + query, + selectedSortOption, + page: 1, + filters, + history, + unionType: newUnionType, + }); }; const onChangePage = (newPage: number) => { scrollToTop(); - navigateToSearchUrl({ type: activeType, query, page: newPage, filters, history, unionType }); + navigateToSearchUrl({ + type: activeType, + query, + selectedSortOption, + page: newPage, + filters, + history, + unionType, + }); }; /** diff --git a/datahub-web-react/src/app/search/SearchResults.tsx b/datahub-web-react/src/app/search/SearchResults.tsx index 61d5ba3cd98cd..4885715fe200f 100644 --- a/datahub-web-react/src/app/search/SearchResults.tsx +++ b/datahub-web-react/src/app/search/SearchResults.tsx @@ -25,6 +25,7 @@ import { SidebarProvider } from './sidebar/SidebarContext'; import { BrowseProvider } from './sidebar/BrowseContext'; import { useIsBrowseV2, useIsSearchV2 } from './useSearchAndBrowseVersion'; import useToggleSidebar from './useToggleSidebar'; +import SearchSortSelect from './sorting/SearchSortSelect'; const SearchResultsWrapper = styled.div<{ v2Styles: boolean }>` display: flex; @@ -209,6 +210,7 @@ export const SearchResults = ({ + const currentQuery: string = isSearchResultPage(location.pathname) ? decodeURIComponent(params.query ? (params.query as string) : '') : ''; + const selectedSortOption = useSelectedSortOption(); const history = useHistory(); const entityRegistry = useEntityRegistry(); @@ -87,6 +89,7 @@ export const SearchablePage = ({ onSearch, onAutoComplete, children }: Props) => query, filters: appliedFilters, history, + selectedSortOption, }); }; diff --git a/datahub-web-react/src/app/search/autoComplete/AutoCompleteEntity.tsx b/datahub-web-react/src/app/search/autoComplete/AutoCompleteEntity.tsx index 1942497057304..8a87407b7176b 100644 --- a/datahub-web-react/src/app/search/autoComplete/AutoCompleteEntity.tsx +++ b/datahub-web-react/src/app/search/autoComplete/AutoCompleteEntity.tsx @@ -63,7 +63,7 @@ export default function AutoCompleteEntity({ query, entity, hasParentTooltip }: const subtype = genericEntityProps?.subTypes?.typeNames?.[0]; return ( - + {icon} diff --git a/datahub-web-react/src/app/search/context/SearchContext.tsx b/datahub-web-react/src/app/search/context/SearchContext.tsx new file mode 100644 index 0000000000000..ec9a0c895e876 --- /dev/null +++ b/datahub-web-react/src/app/search/context/SearchContext.tsx @@ -0,0 +1,23 @@ +import React, { useContext } from 'react'; + +export type SearchContextType = { + selectedSortOption: string | undefined; + setSelectedSortOption: (sortOption: string) => void; +}; + +export const DEFAULT_CONTEXT = { + selectedSortOption: undefined, + setSelectedSortOption: (_: string) => null, +}; + +export const SearchContext = React.createContext(DEFAULT_CONTEXT); + +export function useSearchContext() { + const context = useContext(SearchContext); + if (context === null) throw new Error(`${useSearchContext.name} must be used under a SearchContextProvider`); + return context; +} + +export function useSelectedSortOption() { + return useSearchContext().selectedSortOption; +} diff --git a/datahub-web-react/src/app/search/context/SearchContextProvider.tsx b/datahub-web-react/src/app/search/context/SearchContextProvider.tsx new file mode 100644 index 0000000000000..bfb65c1d74d3e --- /dev/null +++ b/datahub-web-react/src/app/search/context/SearchContextProvider.tsx @@ -0,0 +1,22 @@ +import * as QueryString from 'query-string'; +import { useHistory, useLocation } from 'react-router'; +import React, { useMemo } from 'react'; +import { SearchContext } from './SearchContext'; +import { updateUrlParam } from '../../shared/updateUrlParam'; + +export default function SearchContextProvider({ children }: { children: React.ReactNode }) { + const history = useHistory(); + const location = useLocation(); + const params = useMemo(() => QueryString.parse(location.search, { arrayFormat: 'comma' }), [location.search]); + const selectedSortOption = params.sortOption as string | undefined; + + function setSelectedSortOption(selectedOption: string) { + updateUrlParam(history, 'sortOption', selectedOption); + } + + return ( + + {children} + + ); +} diff --git a/datahub-web-react/src/app/search/context/constants.ts b/datahub-web-react/src/app/search/context/constants.ts new file mode 100644 index 0000000000000..372230db023e9 --- /dev/null +++ b/datahub-web-react/src/app/search/context/constants.ts @@ -0,0 +1,18 @@ +import { SortOrder } from '../../../types.generated'; + +export const RELEVANCE = 'relevance'; +export const NAME_FIELD = 'name'; +export const LAST_OPERATION_TIME_FIELD = 'lastOperationTime'; + +export const DEFAULT_SORT_OPTION = RELEVANCE; + +export const SORT_OPTIONS = { + [RELEVANCE]: { label: 'Relevance', field: RELEVANCE, sortOrder: SortOrder.Descending }, + [`${NAME_FIELD}_${SortOrder.Ascending}`]: { label: 'A to Z', field: NAME_FIELD, sortOrder: SortOrder.Ascending }, + [`${NAME_FIELD}_${SortOrder.Descending}`]: { label: 'Z to A', field: NAME_FIELD, sortOrder: SortOrder.Descending }, + [`${LAST_OPERATION_TIME_FIELD}_${SortOrder.Descending}`]: { + label: 'Last Modified in Platform', + field: LAST_OPERATION_TIME_FIELD, + sortOrder: SortOrder.Descending, + }, +}; diff --git a/datahub-web-react/src/app/search/sorting/SearchSortSelect.tsx b/datahub-web-react/src/app/search/sorting/SearchSortSelect.tsx new file mode 100644 index 0000000000000..683292a20b5b4 --- /dev/null +++ b/datahub-web-react/src/app/search/sorting/SearchSortSelect.tsx @@ -0,0 +1,51 @@ +import Icon, { CaretDownFilled } from '@ant-design/icons'; +import { Select } from 'antd'; +import React from 'react'; +import styled from 'styled-components'; +import { ReactComponent as SortIcon } from '../../../images/sort.svg'; +import { DEFAULT_SORT_OPTION, SORT_OPTIONS } from '../context/constants'; +import { useSearchContext } from '../context/SearchContext'; + +const SelectWrapper = styled.span` + display: inline-flex; + align-items: center; + margin-right: 8px; + + .ant-select-selection-item { + // !important is necessary because updating Select styles for antd is impossible + color: ${(props) => props.theme.styles['primary-color']} !important; + font-weight: 700; + } + + svg { + color: ${(props) => props.theme.styles['primary-color']}; + } +`; + +const StyledIcon = styled(Icon)` + color: ${(props) => props.theme.styles['primary-color']}; + font-size: 16px; + margin-right: -6px; +`; + +export default function SearchSortSelect() { + const { selectedSortOption, setSelectedSortOption } = useSearchContext(); + + const options = Object.entries(SORT_OPTIONS).map(([value, option]) => ({ value, label: option.label })); + + return ( + + +