From b81e818e47d6d16330693265fd87a590446c7131 Mon Sep 17 00:00:00 2001 From: david-leifker <114954101+david-leifker@users.noreply.github.com> Date: Mon, 2 Oct 2023 12:08:37 -0500 Subject: [PATCH 1/8] feat(openapi): openapi v2 updates (#8927) --- build.gradle | 4 +- .../io/datahubproject/OpenApiEntities.java | 29 ++- .../src/main/resources/application.yml | 2 + .../health/config/SpringWebConfig.java | 35 ---- .../delegates/EntityApiDelegateImpl.java | 197 +++++++++++++++++- .../openapi/util/OpenApiEntitiesUtil.java | 8 +- .../OpenAPIEntityTestConfiguration.java | 19 +- .../delegates/EntityApiDelegateImplTest.java | 54 ++++- .../0.0.0-dev/entity-registry.yaml | 8 + .../0.0.0-dev/metadata-models-custom.jar | Bin 0 -> 20878 bytes .../openapi/config/SpringWebConfig.java | 25 +++ .../{ => health}/HealthController.java | 2 +- .../openapi/util/MappingUtil.java | 119 ++++++++--- .../webapp/WEB-INF/healthServlet-servlet.xml | 14 -- .../webapp/WEB-INF/openapiServlet-servlet.xml | 4 +- .../war/src/main/webapp/WEB-INF/web.xml | 8 +- 16 files changed, 428 insertions(+), 100 deletions(-) delete mode 100644 metadata-service/health-servlet/src/main/java/com/datahub/health/config/SpringWebConfig.java create mode 100644 metadata-service/openapi-entity-servlet/src/test/resources/custom-model/mycompany-dq-model/0.0.0-dev/entity-registry.yaml create mode 100644 metadata-service/openapi-entity-servlet/src/test/resources/custom-model/mycompany-dq-model/0.0.0-dev/metadata-models-custom.jar rename metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/{ => health}/HealthController.java (94%) delete mode 100644 metadata-service/war/src/main/webapp/WEB-INF/healthServlet-servlet.xml diff --git a/build.gradle b/build.gradle index c8892045a6683..025c588da2b52 100644 --- a/build.gradle +++ b/build.gradle @@ -200,8 +200,8 @@ project.ext.externalDependency = [ 'springBootStarterValidation': "org.springframework.boot:spring-boot-starter-validation:$springBootVersion", 'springKafka': 'org.springframework.kafka:spring-kafka:2.8.11', 'springActuator': "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion", - 'swaggerAnnotations': 'io.swagger.core.v3:swagger-annotations:2.1.12', - 'swaggerCli': 'io.swagger.codegen.v3:swagger-codegen-cli:3.0.41', + 'swaggerAnnotations': 'io.swagger.core.v3:swagger-annotations:2.2.15', + 'swaggerCli': 'io.swagger.codegen.v3:swagger-codegen-cli:3.0.46', 'testngJava8': 'org.testng:testng:7.5.1', 'testng': 'org.testng:testng:7.8.0', 'testContainers': 'org.testcontainers:testcontainers:' + testContainersVersion, diff --git a/buildSrc/src/main/java/io/datahubproject/OpenApiEntities.java b/buildSrc/src/main/java/io/datahubproject/OpenApiEntities.java index 7fbf013384b7d..888c4a0e99931 100644 --- a/buildSrc/src/main/java/io/datahubproject/OpenApiEntities.java +++ b/buildSrc/src/main/java/io/datahubproject/OpenApiEntities.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; +import com.google.common.collect.ImmutableSet; import com.linkedin.metadata.models.registry.config.Entities; import com.linkedin.metadata.models.registry.config.Entity; import org.gradle.internal.Pair; @@ -16,7 +17,12 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -37,10 +43,23 @@ public class OpenApiEntities { private String entityRegistryYaml; private Path combinedDirectory; - private final static Set SUPPORTED_ASPECT_PATHS = Set.of( - "domains", "ownership", "deprecation", "status", "globalTags", "glossaryTerms", "dataContractInfo", - "browsePathsV2" - ); + private final static ImmutableSet SUPPORTED_ASPECT_PATHS = ImmutableSet.builder() + .add("domains") + .add("ownership") + .add("deprecation") + .add("status") + .add("globalTags") + .add("glossaryTerms") + .add("dataContractInfo") + .add("browsePathsV2") + .add("datasetProperties").add("editableDatasetProperties") + .add("chartInfo").add("editableChartProperties") + .add("dashboardInfo").add("editableDashboardProperties") + .add("notebookInfo").add("editableNotebookProperties") + .add("dataProductProperties") + .add("institutionalMemory") + .build(); + public OpenApiEntities(JsonNodeFactory NODE_FACTORY) { this.NODE_FACTORY = NODE_FACTORY; diff --git a/metadata-service/configuration/src/main/resources/application.yml b/metadata-service/configuration/src/main/resources/application.yml index f180a3f42b730..4be31b2b6bb15 100644 --- a/metadata-service/configuration/src/main/resources/application.yml +++ b/metadata-service/configuration/src/main/resources/application.yml @@ -351,3 +351,5 @@ cache: status: 20 corpUserCredentials: 20 corpUserSettings: 20 + +springdoc.api-docs.groups.enabled: true \ No newline at end of file diff --git a/metadata-service/health-servlet/src/main/java/com/datahub/health/config/SpringWebConfig.java b/metadata-service/health-servlet/src/main/java/com/datahub/health/config/SpringWebConfig.java deleted file mode 100644 index 76d9a6744c4cf..0000000000000 --- a/metadata-service/health-servlet/src/main/java/com/datahub/health/config/SpringWebConfig.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.datahub.health.config; - -import io.swagger.v3.oas.annotations.OpenAPIDefinition; -import io.swagger.v3.oas.annotations.info.Info; -import io.swagger.v3.oas.annotations.servers.Server; -import java.util.List; -import org.springframework.context.annotation.Configuration; -import org.springframework.format.FormatterRegistry; -import org.springframework.http.converter.ByteArrayHttpMessageConverter; -import org.springframework.http.converter.FormHttpMessageConverter; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.http.converter.StringHttpMessageConverter; -import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - - -@EnableWebMvc -@OpenAPIDefinition(info = @Info(title = "DataHub OpenAPI", version = "1.0.0"), - servers = {@Server(url = "/health/", description = "Default Server URL")}) -@Configuration -public class SpringWebConfig implements WebMvcConfigurer { - - @Override - public void configureMessageConverters(List> messageConverters) { - messageConverters.add(new StringHttpMessageConverter()); - messageConverters.add(new ByteArrayHttpMessageConverter()); - messageConverters.add(new FormHttpMessageConverter()); - messageConverters.add(new MappingJackson2HttpMessageConverter()); - } - - @Override - public void addFormatters(FormatterRegistry registry) { - } -} diff --git a/metadata-service/openapi-entity-servlet/src/main/java/io/datahubproject/openapi/delegates/EntityApiDelegateImpl.java b/metadata-service/openapi-entity-servlet/src/main/java/io/datahubproject/openapi/delegates/EntityApiDelegateImpl.java index 5d1065e80d419..ade49c876f168 100644 --- a/metadata-service/openapi-entity-servlet/src/main/java/io/datahubproject/openapi/delegates/EntityApiDelegateImpl.java +++ b/metadata-service/openapi-entity-servlet/src/main/java/io/datahubproject/openapi/delegates/EntityApiDelegateImpl.java @@ -14,22 +14,34 @@ import io.datahubproject.openapi.dto.UrnResponseMap; import io.datahubproject.openapi.entities.EntitiesController; import com.datahub.authorization.AuthorizerChain; +import io.datahubproject.openapi.exception.UnauthorizedException; import io.datahubproject.openapi.generated.BrowsePathsV2AspectRequestV2; import io.datahubproject.openapi.generated.BrowsePathsV2AspectResponseV2; +import io.datahubproject.openapi.generated.ChartInfoAspectRequestV2; +import io.datahubproject.openapi.generated.ChartInfoAspectResponseV2; +import io.datahubproject.openapi.generated.DataProductPropertiesAspectRequestV2; +import io.datahubproject.openapi.generated.DataProductPropertiesAspectResponseV2; +import io.datahubproject.openapi.generated.DatasetPropertiesAspectRequestV2; +import io.datahubproject.openapi.generated.DatasetPropertiesAspectResponseV2; import io.datahubproject.openapi.generated.DeprecationAspectRequestV2; import io.datahubproject.openapi.generated.DeprecationAspectResponseV2; import io.datahubproject.openapi.generated.DomainsAspectRequestV2; import io.datahubproject.openapi.generated.DomainsAspectResponseV2; +import io.datahubproject.openapi.generated.EditableChartPropertiesAspectRequestV2; +import io.datahubproject.openapi.generated.EditableChartPropertiesAspectResponseV2; +import io.datahubproject.openapi.generated.EditableDatasetPropertiesAspectRequestV2; +import io.datahubproject.openapi.generated.EditableDatasetPropertiesAspectResponseV2; import io.datahubproject.openapi.generated.GlobalTagsAspectRequestV2; import io.datahubproject.openapi.generated.GlobalTagsAspectResponseV2; import io.datahubproject.openapi.generated.GlossaryTermsAspectRequestV2; import io.datahubproject.openapi.generated.GlossaryTermsAspectResponseV2; +import io.datahubproject.openapi.generated.InstitutionalMemoryAspectRequestV2; +import io.datahubproject.openapi.generated.InstitutionalMemoryAspectResponseV2; import io.datahubproject.openapi.generated.OwnershipAspectRequestV2; import io.datahubproject.openapi.generated.OwnershipAspectResponseV2; import io.datahubproject.openapi.generated.SortOrder; import io.datahubproject.openapi.generated.StatusAspectRequestV2; import io.datahubproject.openapi.generated.StatusAspectResponseV2; -import io.datahubproject.openapi.exception.UnauthorizedException; import io.datahubproject.openapi.util.OpenApiEntitiesUtil; import com.datahub.authorization.ConjunctivePrivilegeGroup; import com.datahub.authorization.DisjunctivePrivilegeGroup; @@ -408,4 +420,187 @@ private void checkScrollAuthorized(Authentication authentication, EntitySpec ent throw new UnauthorizedException(actorUrnStr + " is unauthorized to get entities."); } } + + public ResponseEntity createDatasetProperties(@Valid DatasetPropertiesAspectRequestV2 body, String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return createAspect(urn, methodNameToAspectName(methodName), body, DatasetPropertiesAspectRequestV2.class, + DatasetPropertiesAspectResponseV2.class); + } + + public ResponseEntity createEditableDatasetProperties( + @Valid EditableDatasetPropertiesAspectRequestV2 body, String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return createAspect(urn, methodNameToAspectName(methodName), body, EditableDatasetPropertiesAspectRequestV2.class, + EditableDatasetPropertiesAspectResponseV2.class); + } + + public ResponseEntity createInstitutionalMemory( + @Valid InstitutionalMemoryAspectRequestV2 body, String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return createAspect(urn, methodNameToAspectName(methodName), body, InstitutionalMemoryAspectRequestV2.class, + InstitutionalMemoryAspectResponseV2.class); + } + + public ResponseEntity createChartInfo(@Valid ChartInfoAspectRequestV2 body, String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return createAspect(urn, methodNameToAspectName(methodName), body, ChartInfoAspectRequestV2.class, + ChartInfoAspectResponseV2.class); + } + + public ResponseEntity createEditableChartProperties( + @Valid EditableChartPropertiesAspectRequestV2 body, String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return createAspect(urn, methodNameToAspectName(methodName), body, EditableChartPropertiesAspectRequestV2.class, + EditableChartPropertiesAspectResponseV2.class); + } + + public ResponseEntity createDataProductProperties( + @Valid DataProductPropertiesAspectRequestV2 body, String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return createAspect(urn, methodNameToAspectName(methodName), body, DataProductPropertiesAspectRequestV2.class, + DataProductPropertiesAspectResponseV2.class); + } + + public ResponseEntity deleteDatasetProperties(String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return deleteAspect(urn, methodNameToAspectName(methodName)); + } + + public ResponseEntity deleteEditableDatasetProperties(String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return deleteAspect(urn, methodNameToAspectName(methodName)); + } + + public ResponseEntity deleteInstitutionalMemory(String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return deleteAspect(urn, methodNameToAspectName(methodName)); + } + + public ResponseEntity deleteChartInfo(String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return deleteAspect(urn, methodNameToAspectName(methodName)); + } + + public ResponseEntity getDatasetProperties(String urn, Boolean systemMetadata) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return getAspect(urn, systemMetadata, methodNameToAspectName(methodName), _respClazz, + DatasetPropertiesAspectResponseV2.class); + } + + public ResponseEntity getEditableDatasetProperties(String urn, Boolean systemMetadata) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return getAspect(urn, systemMetadata, methodNameToAspectName(methodName), _respClazz, + EditableDatasetPropertiesAspectResponseV2.class); + } + + public ResponseEntity getInstitutionalMemory(String urn, Boolean systemMetadata) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return getAspect(urn, systemMetadata, methodNameToAspectName(methodName), _respClazz, + InstitutionalMemoryAspectResponseV2.class); + } + + public ResponseEntity getEditableChartProperties(String urn, Boolean systemMetadata) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return getAspect(urn, systemMetadata, methodNameToAspectName(methodName), _respClazz, EditableChartPropertiesAspectResponseV2.class); + } + + public ResponseEntity getChartInfo(String urn, Boolean systemMetadata) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return getAspect(urn, systemMetadata, methodNameToAspectName(methodName), _respClazz, + ChartInfoAspectResponseV2.class); + } + + public ResponseEntity getDataProductProperties(String urn, Boolean systemMetadata) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return getAspect(urn, systemMetadata, methodNameToAspectName(methodName), _respClazz, + DataProductPropertiesAspectResponseV2.class); + } + + public ResponseEntity headDatasetProperties(String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return headAspect(urn, methodNameToAspectName(methodName)); + } + + public ResponseEntity headEditableDatasetProperties(String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return headAspect(urn, methodNameToAspectName(methodName)); + } + + public ResponseEntity headInstitutionalMemory(String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return headAspect(urn, methodNameToAspectName(methodName)); + } + + public ResponseEntity headDataProductProperties(String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return headAspect(urn, methodNameToAspectName(methodName)); + } + + public ResponseEntity headEditableChartProperties(String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return headAspect(urn, methodNameToAspectName(methodName)); + } + + public ResponseEntity headChartInfo(String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return headAspect(urn, methodNameToAspectName(methodName)); + } + + public ResponseEntity deleteEditableChartProperties(String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return deleteAspect(urn, methodNameToAspectName(methodName)); + } + + public ResponseEntity deleteDataProductProperties(String urn) { + String methodName = walker.walk(frames -> frames + .findFirst() + .map(StackWalker.StackFrame::getMethodName)).get(); + return deleteAspect(urn, methodNameToAspectName(methodName)); + } } diff --git a/metadata-service/openapi-entity-servlet/src/main/java/io/datahubproject/openapi/util/OpenApiEntitiesUtil.java b/metadata-service/openapi-entity-servlet/src/main/java/io/datahubproject/openapi/util/OpenApiEntitiesUtil.java index 13c2d83343aa0..205d401dd956d 100644 --- a/metadata-service/openapi-entity-servlet/src/main/java/io/datahubproject/openapi/util/OpenApiEntitiesUtil.java +++ b/metadata-service/openapi-entity-servlet/src/main/java/io/datahubproject/openapi/util/OpenApiEntitiesUtil.java @@ -54,7 +54,7 @@ public static UpsertAspectRequest convertAspectToUpsert(String entityUrn, Ob if (aspectRequest != null) { // i.e. GlobalTags Method valueMethod = REFLECT.lookupMethod(aspectRequestClazz, "getValue"); - Object aspect = valueMethod.invoke(aspectRequest); + Object aspect = valueMethod == null ? null : valueMethod.invoke(aspectRequest); if (aspect != null) { builder.aspect((OneOfGenericAspectValue) aspect); @@ -82,13 +82,13 @@ public static List convertEntityToUpsert(Object openapi Method aspectMethod = REFLECT.lookupMethod(fromClazz, "get" + upperAspectName); // i.e. GlobalTagsAspectRequestV2 - Object aspectRequest = aspectMethod.invoke(openapiEntity); + Object aspectRequest = aspectMethod == null ? null : aspectMethod.invoke(openapiEntity); if (aspectRequest != null) { Class aspectRequestClazz = REFLECT.lookupClass(upperAspectName + ASPECT_REQUEST_SUFFIX); // i.e. GlobalTags Method valueMethod = REFLECT.lookupMethod(aspectRequestClazz, "getValue"); - Object aspect = valueMethod.invoke(aspectRequest); + Object aspect = valueMethod == null ? null : valueMethod.invoke(aspectRequest); if (aspect != null) { builder.aspect((OneOfGenericAspectValue) aspect); @@ -109,7 +109,7 @@ public static Optional convertAspect(UrnResponseMap urnResponseMap, St return convertEntity(urnResponseMap, entityClazz, withSystemMetadata).map(entity -> { try { Method aspectMethod = REFLECT.lookupMethod(entityClazz, "get" + toUpperFirst(aspectName)); - return aspectClazz.cast(aspectMethod.invoke(entity)); + return aspectMethod == null ? null : aspectClazz.cast(aspectMethod.invoke(entity)); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } diff --git a/metadata-service/openapi-entity-servlet/src/test/java/io/datahubproject/openapi/config/OpenAPIEntityTestConfiguration.java b/metadata-service/openapi-entity-servlet/src/test/java/io/datahubproject/openapi/config/OpenAPIEntityTestConfiguration.java index b7e255b8c270e..cabaa2cbd75e6 100644 --- a/metadata-service/openapi-entity-servlet/src/test/java/io/datahubproject/openapi/config/OpenAPIEntityTestConfiguration.java +++ b/metadata-service/openapi-entity-servlet/src/test/java/io/datahubproject/openapi/config/OpenAPIEntityTestConfiguration.java @@ -13,6 +13,9 @@ import com.linkedin.metadata.models.registry.ConfigEntityRegistry; import com.linkedin.metadata.models.registry.EntityRegistry; import com.linkedin.metadata.models.registry.EntityRegistryException; +import com.linkedin.metadata.models.registry.MergedEntityRegistry; +import com.linkedin.metadata.models.registry.PluginEntityRegistryLoader; +import com.linkedin.metadata.models.registry.SnapshotEntityRegistry; import com.linkedin.metadata.search.ScrollResult; import com.linkedin.metadata.search.SearchEntityArray; import com.linkedin.metadata.search.SearchService; @@ -87,9 +90,21 @@ public AuthorizerChain authorizerChain() { @Bean("entityRegistry") @Primary - public ConfigEntityRegistry configEntityRegistry() throws EntityRegistryException { - return new ConfigEntityRegistry( + public EntityRegistry entityRegistry() throws EntityRegistryException, InterruptedException { + /* + Considered a few different approach to loading a custom model. Chose this method + to as closely match a production configuration rather than direct project to project + dependency. + */ + PluginEntityRegistryLoader custom = new PluginEntityRegistryLoader( + getClass().getResource("/custom-model").getFile()); + + ConfigEntityRegistry standard = new ConfigEntityRegistry( OpenAPIEntityTestConfiguration.class.getClassLoader().getResourceAsStream("entity-registry.yml")); + MergedEntityRegistry entityRegistry = new MergedEntityRegistry(SnapshotEntityRegistry.getInstance()).apply(standard); + custom.withBaseRegistry(entityRegistry).start(true); + + return entityRegistry; } /* Controllers not under this module */ diff --git a/metadata-service/openapi-entity-servlet/src/test/java/io/datahubproject/openapi/delegates/EntityApiDelegateImplTest.java b/metadata-service/openapi-entity-servlet/src/test/java/io/datahubproject/openapi/delegates/EntityApiDelegateImplTest.java index fc2aae1a75ab8..57803ac904a93 100644 --- a/metadata-service/openapi-entity-servlet/src/test/java/io/datahubproject/openapi/delegates/EntityApiDelegateImplTest.java +++ b/metadata-service/openapi-entity-servlet/src/test/java/io/datahubproject/openapi/delegates/EntityApiDelegateImplTest.java @@ -1,6 +1,7 @@ package io.datahubproject.openapi.delegates; import com.linkedin.data.schema.annotation.PathSpecBasedSchemaAnnotationVisitor; +import com.linkedin.metadata.models.registry.EntityRegistry; import io.datahubproject.openapi.config.OpenAPIEntityTestConfiguration; import io.datahubproject.openapi.config.SpringWebConfig; import io.datahubproject.openapi.generated.BrowsePathEntry; @@ -31,24 +32,30 @@ import io.datahubproject.openapi.generated.controller.ChartApiController; import io.datahubproject.openapi.generated.controller.DatasetApiController; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Import; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.test.context.testng.AbstractTestNGSpringContextTests; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import java.util.List; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.testng.Assert.*; @SpringBootTest(classes = {SpringWebConfig.class}) @ComponentScan(basePackages = {"io.datahubproject.openapi.generated.controller"}) @Import({OpenAPIEntityTestConfiguration.class}) +@AutoConfigureMockMvc public class EntityApiDelegateImplTest extends AbstractTestNGSpringContextTests { @BeforeTest public void disableAssert() { @@ -60,11 +67,18 @@ public void disableAssert() { private ChartApiController chartApiController; @Autowired private DatasetApiController datasetApiController; + @Autowired + private EntityRegistry entityRegistry; + @Autowired + private MockMvc mockMvc; @Test public void initTest() { assertNotNull(chartApiController); assertNotNull(datasetApiController); + + assertTrue(entityRegistry.getEntitySpec("dataset").getAspectSpecMap().containsKey("customDataQualityRules"), + "Failed to load custom model from custom registry"); } @Test @@ -200,4 +214,40 @@ public void glossaryTermsTest() { assertEquals(datasetApiController.getGlossaryTerms(testUrn, false).getStatusCode(), HttpStatus.NOT_FOUND); assertEquals(datasetApiController.headGlossaryTerms(testUrn).getStatusCode(), HttpStatus.NOT_FOUND); } + + + /** + * The purpose of this test is to ensure no errors when a custom aspect is encountered, + * not that the custom aspect is processed. The missing piece to support custom + * aspects is the openapi generated classes for the custom aspects and related request/responses. + */ + @Test + public void customModelTest() throws Exception { + String expectedUrn = "urn:li:dataset:(urn:li:dataPlatform:hive,SampleHiveDataset,PROD)"; + + //CHECKSTYLE:OFF + String body = "[\n" + + " {\n" + + " \"urn\": \"" + expectedUrn + "\",\n" + + " \"customDataQualityRules\": [\n" + + " {\n" + + " \"field\": \"my_event_data\",\n" + + " \"isFieldLevel\": false,\n" + + " \"type\": \"isNull\",\n" + + " \"checkDefinition\": \"n/a\",\n" + + " \"url\": \"https://github.com/datahub-project/datahub/blob/master/checks/nonNull.sql\"\n" + + " }\n" + + " ]\n" + + " }\n" + + "]"; + //CHECKSTYLE:ON + + mockMvc.perform(MockMvcRequestBuilders + .post("/v2/entity/dataset") + .content(body) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON)) + .andExpect(status().is2xxSuccessful()) + .andExpect(MockMvcResultMatchers.jsonPath("$.[0].urn").value(expectedUrn)); + } } diff --git a/metadata-service/openapi-entity-servlet/src/test/resources/custom-model/mycompany-dq-model/0.0.0-dev/entity-registry.yaml b/metadata-service/openapi-entity-servlet/src/test/resources/custom-model/mycompany-dq-model/0.0.0-dev/entity-registry.yaml new file mode 100644 index 0000000000000..2b501946ca858 --- /dev/null +++ b/metadata-service/openapi-entity-servlet/src/test/resources/custom-model/mycompany-dq-model/0.0.0-dev/entity-registry.yaml @@ -0,0 +1,8 @@ +id: mycompany-dq-model +entities: + - name: dataset + aspects: + - customDataQualityRules + - name: container + aspects: + - customDataQualityRules \ No newline at end of file diff --git a/metadata-service/openapi-entity-servlet/src/test/resources/custom-model/mycompany-dq-model/0.0.0-dev/metadata-models-custom.jar b/metadata-service/openapi-entity-servlet/src/test/resources/custom-model/mycompany-dq-model/0.0.0-dev/metadata-models-custom.jar new file mode 100644 index 0000000000000000000000000000000000000000..7a5cfb325987d51432b4d9ae6b922ecf49b80ef3 GIT binary patch literal 20878 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
q$oDZ4aS60 z3V!p?^vAP1$X7lW(xxnsl`~tohR)<*Jm^gIS}-2c6`5#ULfS%~hn!Isuqc{aN;2E$ z@NB!|JZ=Spf3&Kfqk8^+OJdMRovemL(kVI(*{8@+>rmo_;D!Na)BFY$oA85Lhn$`r zum_GVj18l?NkVn7)sI||Hr7VYuDMBe1OJLt9THS!60@t!1xKdGCw@o+sxz8H0AMfJ zOsC*WFm8|XMhT+rPuDrdDXg2&M|_KZ;Y7~+qd?mKu+eA1gpvt)9rgr_ema?GZkW?Z znMrFut^Ze~eSz!$HhB2j@&V*vYHsN4>ipljF#K0L*#Bbp|7!?={x7zFuQ9|wdRP3P zJNd^I{?qHC}1%I);{a%lKEX#OY1e`yp!=U`%E8_f+5!UzlYmfcUj`mI<`aqb74 z%sv>*2>dI7Xn0pr4Mo1^(BuLIStZst!TRQ6M)smuQod|z)M^ophP{d+2b>c+P&`KN|G5Y3|M}?u@&wNRcm&<|&K*pwmOqPcv zfWX&%K?SvQ^Vg31&IhyHAs!WoX_=RlCs(#|jlK}B{B{DgLqHo)yt9}ojFRa0%SFQ< ze1%xT*oB1jRVhC)F9n*&rcHJwNRJYA8fnU^sLj0|rVTpD)Qf??&k;;OXM)oN8()ME zNcaupC96gI*ZqmSZ&P0xSSGQ@Xn-U$)g0)xpj6*nGjy z*i+%3AXGNCFts)OpRxE4r2Jc_{~s`k|0l=)#M{4iSNUH9>VoGN&;Tw@yAeiUNwQCE z4#n&T>YfjbaBHGX!4mt$$+;5@`41@ww0{4`b?D9)_8l`VW^dUg*)Du;iC|PqvV!mb z-50VP9~T{dy!&{yd1y}|7n|j6!@R(ruuoQ<%r8Q17#v?aeigA=f8l9*y2F|tiPFW+ zbJ*H0oOpifPIbs7)dz=`X-rvHwpoj5O~6c!HLV|xExIS1^<7^yzQgj|%-HGYzn@#M z;LWLnmv1~&xTrR9&GBFAlRxyi{oStbecW^R@)y5EL%*HV;5hy^Mpyf~QQ^N5r5%;5 zX3YFNM}3PXXQA&tn|k28!%ARy>;;AkX!9>|p^FwSz)Vq`ytaQY-ys8@mhX11Q+B>$ zHVF68*`UNWc~`8hh6_wL;M^Uen)zF$;Gw?3xJl`9>m7U;OW z!?ePJZNihsnjU|4oNI_saw-oMU!Li3bXBF^%xM8;daUPiX0(`=e0z4|*S^owuDsRF ze(b3|;p8W?2TS@=-ITWbuVMbCVf-L8yK}*hZ?9y2UrMZYz7uGZmE#%s>f3bornA-m zpKb6q+y^WP7@0(vai8-83{mJ-6XA)Nt)ZURU( zu*m@iz-9*&fGjQpvO(DipAOK$KM(-YF$>5)mO_w@2tqd){Y(ag!I#;wxC_l-)Ds%e%|_qajxhTLF#a+8jAl0Ojqd37 zpl>Ng*mHxIustXnjM0ro-yVuEI$eMWqp?jFqg#Q#Qw(9ne&BIdghC0rZw%c~^tH7J zLm$Z#VJK|%2D-86D;p8U?p7jfEY?#C(5*mUv4*h1O_i_}#YEYGzL*7JM?bLiAQa@d z0tbCH0m9T2U^9)7sl_V9Bo_4fRD>PJ^aarDj?!pyh!gw4e4ilLhiG7nbJ zf|}eA04n;zoe<4le7ZmlaD=XUBwe6T#itY097pI}jiwXPX2E9$sL6#e;~`EnuoT2mr;`H5VLB6nsXY z1|PR84kHM~B&hw0aES{(V~C4ZP-_-pU^{UJVu@q)wk^!mC5>l^HWRJt14T=5GJ2q* lHx>~VJ$1tqsEE)2Iv&j;s6DJ~AhYCva1tj2!!vgf4*(!CVJ83p literal 0 HcmV?d00001 diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/config/SpringWebConfig.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/config/SpringWebConfig.java index 9feb9c8e5640f..71e8c79a2275a 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/config/SpringWebConfig.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/config/SpringWebConfig.java @@ -5,6 +5,9 @@ import io.swagger.v3.oas.annotations.info.Info; import io.swagger.v3.oas.annotations.servers.Server; import java.util.List; + +import org.springdoc.core.GroupedOpenApi; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.http.converter.ByteArrayHttpMessageConverter; @@ -34,4 +37,26 @@ public void configureMessageConverters(List> messageConv public void addFormatters(FormatterRegistry registry) { registry.addConverter(new StringToChangeCategoryConverter()); } + + @Bean + public GroupedOpenApi defaultOpenApiGroup() { + return GroupedOpenApi.builder() + .group("default") + .packagesToExclude( + "io.datahubproject.openapi.operations", + "com.datahub.health", + "io.datahubproject.openapi.health" + ).build(); + } + + @Bean + public GroupedOpenApi operationsOpenApiGroup() { + return GroupedOpenApi.builder() + .group("operations") + .packagesToScan( + "io.datahubproject.openapi.operations", + "com.datahub.health", + "io.datahubproject.openapi.health" + ).build(); + } } diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/HealthController.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/health/HealthController.java similarity index 94% rename from metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/HealthController.java rename to metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/health/HealthController.java index 250e9f6f71242..2e243f4c8df9e 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/HealthController.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/health/HealthController.java @@ -1,4 +1,4 @@ -package io.datahubproject.openapi; +package io.datahubproject.openapi.health; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; diff --git a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/util/MappingUtil.java b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/util/MappingUtil.java index 68a8c8ca49235..2b3e84e2df20f 100644 --- a/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/util/MappingUtil.java +++ b/metadata-service/openapi-servlet/src/main/java/io/datahubproject/openapi/util/MappingUtil.java @@ -48,6 +48,7 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -91,10 +92,11 @@ private MappingUtil() { private static final String DISCRIMINATOR = "__type"; private static final String PEGASUS_PACKAGE = "com.linkedin"; + private static final String OPENAPI_PACKAGE = "io.datahubproject.openapi.generated"; private static final ReflectionCache REFLECT_AVRO = ReflectionCache.builder() .basePackage("com.linkedin.pegasus2avro").build(); private static final ReflectionCache REFLECT_OPENAPI = ReflectionCache.builder() - .basePackage("io.datahubproject.openapi.generated").build(); + .basePackage(OPENAPI_PACKAGE).build(); static { // Build a map from __type name to generated class @@ -143,49 +145,108 @@ public static EnvelopedAspect mapEnvelopedAspect(com.linkedin.entity.EnvelopedAs } private static DataMap insertDiscriminator(@Nullable Class parentClazz, DataMap dataMap) { - if (REFLECT_OPENAPI.lookupMethod(parentClazz, "get__type") != null) { + if (parentClazz != null && REFLECT_OPENAPI.lookupMethod(parentClazz, "get__type") != null) { dataMap.put(DISCRIMINATOR, parentClazz.getSimpleName()); } Set> requiresDiscriminator = dataMap.entrySet().stream() .filter(e -> e.getValue() instanceof DataMap) - .filter(e -> e.getKey().startsWith(PEGASUS_PACKAGE + ".")) + .filter(e -> shouldCollapseClassToDiscriminator(e.getKey())) .map(e -> Map.entry(e.getKey(), (DataMap) e.getValue())) .collect(Collectors.toSet()); + // DataMap doesn't support concurrent access requiresDiscriminator.forEach(e -> { dataMap.remove(e.getKey()); - dataMap.put(DISCRIMINATOR, e.getKey().substring(e.getKey().lastIndexOf('.') + 1)); + dataMap.put(DISCRIMINATOR, e.getKey().substring(e.getKey().lastIndexOf(".") + 1)); dataMap.putAll(e.getValue()); }); - Set> recurse = dataMap.entrySet().stream() - .filter(e -> e.getValue() instanceof DataMap || e.getValue() instanceof DataList) - .flatMap(e -> { - if (e.getValue() instanceof DataList) { - return ((DataList) e.getValue()).stream() - .filter(item -> item instanceof DataMap) - .map(item -> Pair.of((String) null, (DataMap) item)); - } else { - return Stream.of(Pair.of(e.getKey(), (DataMap) e.getValue())); + // Look through all the nested classes for possible discriminator requirements + Set, DataMap>> nestedDataMaps = getDataMapPaths(new LinkedList<>(), dataMap).collect(Collectors.toSet()); + // DataMap doesn't support concurrent access + for (Pair, DataMap> nestedDataMapPath : nestedDataMaps) { + List nestedPath = nestedDataMapPath.getFirst(); + DataMap nested = nestedDataMapPath.getSecond(); + Class nextClazz = parentClazz; + + if (nextClazz != null) { + // reconstruct type path from method path + for (String pathElem : nestedPath) { + // if not list element + if (!pathElem.startsWith("[") && !pathElem.contains(".")) { + String methodName = "get" + toUpperFirst(pathElem); + Method getMethod = REFLECT_OPENAPI.lookupMethod(nextClazz, methodName); + nextClazz = getMethod != null ? getMethod.getReturnType() : null; + + if (nextClazz != null && "List".equals(nextClazz.getSimpleName())) { + String listElemClassName = getMethod.getGenericReturnType().getTypeName() + .replace("java.util.List<", "") + .replace(">", ""); + try { + nextClazz = Class.forName(listElemClassName); + } catch (ClassNotFoundException ex) { + log.warn("Class lookup failed for {}", listElemClassName); + nextClazz = null; } - }).collect(Collectors.toSet()); - - recurse.forEach(e -> { - if (e.getKey() != null) { - Class getterClazz = null; - if (parentClazz != null) { - Method getMethod = REFLECT_OPENAPI.lookupMethod(parentClazz, "get" + toUpperFirst(e.getKey())); - getterClazz = getMethod.getReturnType(); + } + } + } + + if ((nextClazz != parentClazz && shouldCheckTypeMethod(nextClazz)) + || nested.keySet().stream().anyMatch(MappingUtil::shouldCollapseClassToDiscriminator)) { + insertDiscriminator(nextClazz, nested); } - insertDiscriminator(getterClazz, e.getValue()); - } else { - insertDiscriminator(null, e.getValue()); } - }); + } return dataMap; } + + /** + * Stream paths to DataMaps + * @param paths current path + * @param data current DataMap or DataList + * @return path to all nested DataMaps + */ + private static Stream, DataMap>> getDataMapPaths(List paths, Object data) { + if (data instanceof DataMap) { + return ((DataMap) data).entrySet().stream() + .filter(e -> e.getValue() instanceof DataMap || e.getValue() instanceof DataList) + .flatMap(entry -> { + List thisPath = new LinkedList<>(paths); + thisPath.add(entry.getKey()); + if (entry.getValue() instanceof DataMap) { + return Stream.concat( + Stream.of(Pair.of(thisPath, (DataMap) entry.getValue())), + getDataMapPaths(thisPath, entry.getValue()) + ); + } else { + // DataList + return getDataMapPaths(thisPath, entry.getValue()); + } + }); + } else if (data instanceof DataList) { + DataList dataList = (DataList) data; + return IntStream.range(0, dataList.size()) + .mapToObj(idx -> Pair.of(idx, dataList.get(idx))) + .filter(idxObject -> idxObject.getValue() instanceof DataMap || idxObject.getValue() instanceof DataList) + .flatMap(idxObject -> { + Object item = idxObject.getValue(); + List thisPath = new LinkedList<>(paths); + thisPath.add("[" + idxObject.getKey() + "]"); + if (item instanceof DataMap) { + return Stream.concat(Stream.of(Pair.of(thisPath, (DataMap) item)), + getDataMapPaths(thisPath, item)); + } else { + // DataList + return getDataMapPaths(thisPath, item); + } + }); + } + return Stream.empty(); + } + public static OneOfEnvelopedAspectValue mapAspectValue(String aspectName, Aspect aspect, ObjectMapper objectMapper) { Class aspectClass = ENVELOPED_ASPECT_TYPE_MAP.get(aspectName); DataMap wrapper = insertDiscriminator(aspectClass, aspect.data()); @@ -227,6 +288,14 @@ private static String getAspectName(Class cls) { return new String(c); } + private static boolean shouldCheckTypeMethod(@Nullable Class parentClazz) { + return Optional.ofNullable(parentClazz).map(cls -> cls.getName().startsWith(OPENAPI_PACKAGE + ".")).orElse(false); + } + + private static boolean shouldCollapseClassToDiscriminator(String className) { + return className.startsWith(PEGASUS_PACKAGE + "."); + } + private static Optional shouldDiscriminate(String parentShortClass, String fieldName, ObjectNode node) { try { if (parentShortClass != null) { diff --git a/metadata-service/war/src/main/webapp/WEB-INF/healthServlet-servlet.xml b/metadata-service/war/src/main/webapp/WEB-INF/healthServlet-servlet.xml deleted file mode 100644 index 11af7d000bddf..0000000000000 --- a/metadata-service/war/src/main/webapp/WEB-INF/healthServlet-servlet.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - diff --git a/metadata-service/war/src/main/webapp/WEB-INF/openapiServlet-servlet.xml b/metadata-service/war/src/main/webapp/WEB-INF/openapiServlet-servlet.xml index 7c990cee8f65b..3077cfb062638 100644 --- a/metadata-service/war/src/main/webapp/WEB-INF/openapiServlet-servlet.xml +++ b/metadata-service/war/src/main/webapp/WEB-INF/openapiServlet-servlet.xml @@ -2,9 +2,9 @@ - - + + diff --git a/metadata-service/war/src/main/webapp/WEB-INF/web.xml b/metadata-service/war/src/main/webapp/WEB-INF/web.xml index f210061a0bb27..c1239ed4b7ed4 100644 --- a/metadata-service/war/src/main/webapp/WEB-INF/web.xml +++ b/metadata-service/war/src/main/webapp/WEB-INF/web.xml @@ -54,12 +54,6 @@ 1 true - - healthServlet - org.springframework.web.servlet.DispatcherServlet - 1 - true - openapiServlet org.springframework.web.servlet.DispatcherServlet @@ -95,7 +89,7 @@ /health - healthServlet + openapiServlet /health/* From c779b92bd0ca0be3d2a0984ecd035d9bf2ea5d92 Mon Sep 17 00:00:00 2001 From: Lucas Phan Date: Mon, 2 Oct 2023 11:58:58 -0700 Subject: [PATCH 2/8] fix(data-product): show data product card on home page (#8924) --- .../src/app/entity/dataProduct/DataProductEntity.tsx | 2 +- datahub-web-react/src/app/home/HomePageRecommendations.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/datahub-web-react/src/app/entity/dataProduct/DataProductEntity.tsx b/datahub-web-react/src/app/entity/dataProduct/DataProductEntity.tsx index c3f1273681c19..620d42943a74a 100644 --- a/datahub-web-react/src/app/entity/dataProduct/DataProductEntity.tsx +++ b/datahub-web-react/src/app/entity/dataProduct/DataProductEntity.tsx @@ -51,7 +51,7 @@ export class DataProductEntity implements Entity { isSearchEnabled = () => true; - isBrowseEnabled = () => false; + isBrowseEnabled = () => true; isLineageEnabled = () => false; diff --git a/datahub-web-react/src/app/home/HomePageRecommendations.tsx b/datahub-web-react/src/app/home/HomePageRecommendations.tsx index 39d76bf98f28a..6ce7735c4a7c8 100644 --- a/datahub-web-react/src/app/home/HomePageRecommendations.tsx +++ b/datahub-web-react/src/app/home/HomePageRecommendations.tsx @@ -95,6 +95,7 @@ const simpleViewEntityTypes = [ EntityType.Dashboard, EntityType.GlossaryNode, EntityType.GlossaryTerm, + EntityType.DataProduct, ]; export const HomePageRecommendations = ({ user }: Props) => { From 6fe9d6faa55c4fe770b00390e6b64bf7ccb10fd7 Mon Sep 17 00:00:00 2001 From: Harshal Sheth Date: Mon, 2 Oct 2023 16:58:31 -0400 Subject: [PATCH 3/8] fix(graphql): support additional types in scrollAcrossEntities (#8891) --- .../linkedin/datahub/graphql/resolvers/EntityTypeMapper.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/EntityTypeMapper.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/EntityTypeMapper.java index 3682b2282544e..b0f23e63177e6 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/EntityTypeMapper.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/EntityTypeMapper.java @@ -17,6 +17,7 @@ public class EntityTypeMapper { ImmutableMap.builder() .put(EntityType.DATASET, "dataset") .put(EntityType.ROLE, "role") + .put(EntityType.ASSERTION, Constants.ASSERTION_ENTITY_NAME) .put(EntityType.CORP_USER, "corpuser") .put(EntityType.CORP_GROUP, "corpGroup") .put(EntityType.DATA_PLATFORM, "dataPlatform") @@ -25,6 +26,7 @@ public class EntityTypeMapper { .put(EntityType.TAG, "tag") .put(EntityType.DATA_FLOW, "dataFlow") .put(EntityType.DATA_JOB, "dataJob") + .put(EntityType.DATA_PROCESS_INSTANCE, Constants.DATA_PROCESS_INSTANCE_ENTITY_NAME) .put(EntityType.GLOSSARY_TERM, "glossaryTerm") .put(EntityType.GLOSSARY_NODE, "glossaryNode") .put(EntityType.MLMODEL, "mlModel") From acaf950b9e5e2118310a35940e98b0799a8a9bcd Mon Sep 17 00:00:00 2001 From: Harshal Sheth Date: Mon, 2 Oct 2023 16:59:18 -0400 Subject: [PATCH 4/8] docs: update cta links for acryl (#8908) --- .../src/app/entity/shared/components/styled/DemoButton.tsx | 2 +- datahub-web-react/src/app/home/AcrylDemoBanner.tsx | 2 +- docs-website/docusaurus.config.js | 2 +- docs-website/src/pages/_components/CardCTAs/index.js | 6 +++--- docs-website/src/pages/_components/Section/index.js | 2 +- docs/saas.md | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/datahub-web-react/src/app/entity/shared/components/styled/DemoButton.tsx b/datahub-web-react/src/app/entity/shared/components/styled/DemoButton.tsx index 1ed182fa01975..b7b974ef6e2ea 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/DemoButton.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/DemoButton.tsx @@ -12,7 +12,7 @@ export default function DemoButton() { return ( diff --git a/datahub-web-react/src/app/home/AcrylDemoBanner.tsx b/datahub-web-react/src/app/home/AcrylDemoBanner.tsx index 0a6316a71db16..0a85c0c3d7f6c 100644 --- a/datahub-web-react/src/app/home/AcrylDemoBanner.tsx +++ b/datahub-web-react/src/app/home/AcrylDemoBanner.tsx @@ -46,7 +46,7 @@ export default function AcrylDemoBanner() { DataHub is already the industry's #1 Open Source Data Catalog.{' '} diff --git a/docs-website/docusaurus.config.js b/docs-website/docusaurus.config.js index c1ecf0283cf63..68ea1ebffa6c9 100644 --- a/docs-website/docusaurus.config.js +++ b/docs-website/docusaurus.config.js @@ -23,7 +23,7 @@ module.exports = { announcementBar: { id: "announcement", content: - '

Managed DataHub  Acryl Data delivers an easy to consume DataHub platform for the enterprise

Sign up for Managed DataHub →', + '

Managed DataHub  Acryl Data delivers an easy to consume DataHub platform for the enterprise

Sign up for Managed DataHub →', backgroundColor: "#070707", textColor: "#ffffff", isCloseable: false, diff --git a/docs-website/src/pages/_components/CardCTAs/index.js b/docs-website/src/pages/_components/CardCTAs/index.js index d87c803b42818..b173101de66f5 100644 --- a/docs-website/src/pages/_components/CardCTAs/index.js +++ b/docs-website/src/pages/_components/CardCTAs/index.js @@ -8,17 +8,17 @@ const cardsContent = [ { label: "Data Mesh", title: "Data Products, Delivered", - url: "https://www.acryldata.io/blog/data-products-in-datahub-everything-you-need-to-know", + url: "https://www.acryldata.io/blog/data-products-in-datahub-everything-you-need-to-know?utm_source=datahub&utm_medium=referral&utm_content=blog", }, { label: "Data Contracts", title: "End-to-end Reliability in Data", - url: "https://www.acryldata.io/blog/data-contracts-in-datahub-combining-verifiability-with-holistic-data-management", + url: "https://www.acryldata.io/blog/data-contracts-in-datahub-combining-verifiability-with-holistic-data-management?utm_source=datahub&utm_medium=referral&utm_content=blog", }, { label: "Shift Left", title: "Developer-friendly Data Governance", - url: "https://www.acryldata.io/blog/the-3-must-haves-of-metadata-management-part-2", + url: "https://www.acryldata.io/blog/the-3-must-haves-of-metadata-management-part-2?utm_source=datahub&utm_medium=referral&utm_content=blog", }, ]; diff --git a/docs-website/src/pages/_components/Section/index.js b/docs-website/src/pages/_components/Section/index.js index b7e33bad162f9..8fb8dc06937cc 100644 --- a/docs-website/src/pages/_components/Section/index.js +++ b/docs-website/src/pages/_components/Section/index.js @@ -18,7 +18,7 @@ const PromoSection = () => (

Managed DataHub

Acryl Data delivers an easy to consume DataHub platform for the enterprise

- + Sign up for Managed DataHub → diff --git a/docs/saas.md b/docs/saas.md index 35dde5b1ca9a9..de57b5617e062 100644 --- a/docs/saas.md +++ b/docs/saas.md @@ -5,10 +5,10 @@ Sign up for fully managed, hassle-free and secure SaaS service for DataHub, prov

Sign up

-Refer to [Managed Datahub Exclusives](/docs/managed-datahub/managed-datahub-overview.md) for more information. \ No newline at end of file +Refer to [Managed Datahub Exclusives](/docs/managed-datahub/managed-datahub-overview.md) for more information. From 790011d40b9fe97373730e875e00237bd2d97904 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Tue, 3 Oct 2023 04:04:55 +0100 Subject: [PATCH 5/8] feat(docs): Corrects release version for custom ownership types. (#8847) --- docs/ownership/ownership-types.md | 2 +- .../examples/ownership/ownership_type.json | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/docs/ownership/ownership-types.md b/docs/ownership/ownership-types.md index f1b951871a5a2..243f638a324ad 100644 --- a/docs/ownership/ownership-types.md +++ b/docs/ownership/ownership-types.md @@ -7,7 +7,7 @@ import TabItem from '@theme/TabItem'; **🤝 Version compatibility** -> Open Source DataHub: **0.10.3** | Acryl: **0.2.8** +> Open Source DataHub: **0.10.4** | Acryl: **0.2.8** ## What are Custom Ownership Types? Custom Ownership Types are an improvement on the way to establish ownership relationships between users and the data assets they manage within DataHub. diff --git a/metadata-ingestion/examples/ownership/ownership_type.json b/metadata-ingestion/examples/ownership/ownership_type.json index 5f1d3019d2a77..4a194c78a3b72 100644 --- a/metadata-ingestion/examples/ownership/ownership_type.json +++ b/metadata-ingestion/examples/ownership/ownership_type.json @@ -1,7 +1,14 @@ -{ - "urn": "urn:li:ownershipType:architect", - "info": { - "name": "Architect", - "description": "Technical person responsible for the asset" +[ + { + "auditHeader":null, + "entityType":"ownershipType", + "entityUrn": "urn:li:ownershipType:architect", + "changeType":"UPSERT", + "aspectName":"ownershipTypeInfo", + "aspect":{ + "value":"{\"name\": \"Architect\", \"description\": \"Technical person responsible for the asset\", \"created\": {\"time\": 1674291843000, \"actor\": \"urn:li:corpuser:jdoe\", \"impersonator\": null},\n\"lastModified\": {\"time\": 1674291843000, \"actor\": \"urn:li:corpuser:jdoe\", \"impersonator\": null}}", + "contentType":"application/json" + }, + "systemMetadata":null } -} \ No newline at end of file +] \ No newline at end of file From 2f0616ea5b2c1927107a4726773c907a59a0483f Mon Sep 17 00:00:00 2001 From: Erik McKelvey Date: Mon, 2 Oct 2023 20:05:29 -0700 Subject: [PATCH 6/8] docs: fix typo in impact-analysis.md (#8915) --- docs/act-on-metadata/impact-analysis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/act-on-metadata/impact-analysis.md b/docs/act-on-metadata/impact-analysis.md index 2c10e571cf911..9728a480efe32 100644 --- a/docs/act-on-metadata/impact-analysis.md +++ b/docs/act-on-metadata/impact-analysis.md @@ -38,7 +38,7 @@ Follow these simple steps to understand the full dependency chain of your data e

-4. Slice and dice the result list by Entity Type, Platfrom, Owner, and more to isolate the relevant dependencies +4. Slice and dice the result list by Entity Type, Platform, Owner, and more to isolate the relevant dependencies

From 83a7dad20e7420b7283db22a2964d05ee3c42a7d Mon Sep 17 00:00:00 2001 From: Lucas Phan Date: Tue, 3 Oct 2023 10:05:11 -0700 Subject: [PATCH 7/8] =?UTF-8?q?feat(chrom-ext-editable):=20set=20readOnly?= =?UTF-8?q?=20to=20false=20so=20that=20side=20navigati=E2=80=A6=20(#8930)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/app/entity/shared/embed/EmbeddedProfile.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/datahub-web-react/src/app/entity/shared/embed/EmbeddedProfile.tsx b/datahub-web-react/src/app/entity/shared/embed/EmbeddedProfile.tsx index 31a736e30bdc0..df928fc408de6 100644 --- a/datahub-web-react/src/app/entity/shared/embed/EmbeddedProfile.tsx +++ b/datahub-web-react/src/app/entity/shared/embed/EmbeddedProfile.tsx @@ -55,6 +55,8 @@ export default function EmbeddedProfile({ urn, entityType, getOverridePropert return ; } + const readOnly = false; + return ( ({ urn, entityType, getOverridePropert - + - + - + - + - + )} From 0a5e7d176e103c14f36cd00cd1b930c5da55e1ea Mon Sep 17 00:00:00 2001 From: Ellie O'Neil <110510035+eboneil@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:53:05 -0700 Subject: [PATCH 8/8] fix(client): use value for RelationshipDirection (#8912) --- metadata-ingestion/src/datahub/ingestion/graph/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metadata-ingestion/src/datahub/ingestion/graph/client.py b/metadata-ingestion/src/datahub/ingestion/graph/client.py index e22d48d0af80a..673ada4f73051 100644 --- a/metadata-ingestion/src/datahub/ingestion/graph/client.py +++ b/metadata-ingestion/src/datahub/ingestion/graph/client.py @@ -805,7 +805,7 @@ def get_related_entities( url=relationship_endpoint, params={ "urn": entity_urn, - "direction": direction, + "direction": direction.value, "relationshipTypes": relationship_types, "start": start, },