mirror of
https://github.com/datahub-project/datahub.git
synced 2025-09-03 14:23:03 +00:00
feat(summaryTab): add support of created property (#14542)
Co-authored-by: Chris Collins <chriscollins3456@gmail.com>
This commit is contained in:
parent
d74575708d
commit
c7b8da593f
@ -2,6 +2,7 @@ package com.linkedin.datahub.graphql.types.dataproduct;
|
||||
|
||||
import static com.linkedin.metadata.Constants.APPLICATION_MEMBERSHIP_ASPECT_NAME;
|
||||
import static com.linkedin.metadata.Constants.DATA_PRODUCT_ENTITY_NAME;
|
||||
import static com.linkedin.metadata.Constants.DATA_PRODUCT_KEY_ASPECT_NAME;
|
||||
import static com.linkedin.metadata.Constants.DATA_PRODUCT_PROPERTIES_ASPECT_NAME;
|
||||
import static com.linkedin.metadata.Constants.DOMAINS_ASPECT_NAME;
|
||||
import static com.linkedin.metadata.Constants.FORMS_ASPECT_NAME;
|
||||
@ -47,6 +48,7 @@ public class DataProductType
|
||||
com.linkedin.datahub.graphql.types.EntityType<DataProduct, String> {
|
||||
public static final Set<String> ASPECTS_TO_FETCH =
|
||||
ImmutableSet.of(
|
||||
DATA_PRODUCT_KEY_ASPECT_NAME,
|
||||
DATA_PRODUCT_PROPERTIES_ASPECT_NAME,
|
||||
OWNERSHIP_ASPECT_NAME,
|
||||
GLOBAL_TAGS_ASPECT_NAME,
|
||||
|
@ -2,6 +2,7 @@ package com.linkedin.datahub.graphql.types.dataproduct.mappers;
|
||||
|
||||
import static com.linkedin.datahub.graphql.authorization.AuthorizationUtils.canView;
|
||||
import static com.linkedin.metadata.Constants.APPLICATION_MEMBERSHIP_ASPECT_NAME;
|
||||
import static com.linkedin.metadata.Constants.DATA_PRODUCT_KEY_ASPECT_NAME;
|
||||
import static com.linkedin.metadata.Constants.DATA_PRODUCT_PROPERTIES_ASPECT_NAME;
|
||||
import static com.linkedin.metadata.Constants.DOMAINS_ASPECT_NAME;
|
||||
import static com.linkedin.metadata.Constants.FORMS_ASPECT_NAME;
|
||||
@ -24,6 +25,7 @@ import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||
import com.linkedin.datahub.graphql.generated.DataProduct;
|
||||
import com.linkedin.datahub.graphql.generated.EntityType;
|
||||
import com.linkedin.datahub.graphql.generated.ResolvedAuditStamp;
|
||||
import com.linkedin.datahub.graphql.types.application.ApplicationAssociationMapper;
|
||||
import com.linkedin.datahub.graphql.types.common.mappers.CustomPropertiesMapper;
|
||||
import com.linkedin.datahub.graphql.types.common.mappers.InstitutionalMemoryMapper;
|
||||
@ -35,6 +37,7 @@ import com.linkedin.datahub.graphql.types.glossary.mappers.GlossaryTermsMapper;
|
||||
import com.linkedin.datahub.graphql.types.mappers.ModelMapper;
|
||||
import com.linkedin.datahub.graphql.types.structuredproperty.StructuredPropertiesMapper;
|
||||
import com.linkedin.datahub.graphql.types.tag.mappers.GlobalTagsMapper;
|
||||
import com.linkedin.datahub.graphql.util.EntityResponseUtils;
|
||||
import com.linkedin.dataproduct.DataProductProperties;
|
||||
import com.linkedin.domain.Domains;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
@ -61,11 +64,18 @@ public class DataProductMapper implements ModelMapper<EntityResponse, DataProduc
|
||||
result.setUrn(entityResponse.getUrn().toString());
|
||||
result.setType(EntityType.DATA_PRODUCT);
|
||||
|
||||
// Getting of created timestamp from key aspect as we can't get this data in default way
|
||||
ResolvedAuditStamp createdAuditStampFromKeyAspect =
|
||||
EntityResponseUtils.extractAspectCreatedAuditStamp(
|
||||
entityResponse, DATA_PRODUCT_KEY_ASPECT_NAME);
|
||||
|
||||
EnvelopedAspectMap aspectMap = entityResponse.getAspects();
|
||||
MappingHelper<DataProduct> mappingHelper = new MappingHelper<>(aspectMap, result);
|
||||
mappingHelper.mapToResult(
|
||||
DATA_PRODUCT_PROPERTIES_ASPECT_NAME,
|
||||
(dataProduct, dataMap) -> mapDataProductProperties(dataProduct, dataMap, entityUrn));
|
||||
(dataProduct, dataMap) ->
|
||||
mapDataProductProperties(
|
||||
dataProduct, dataMap, entityUrn, createdAuditStampFromKeyAspect));
|
||||
mappingHelper.mapToResult(
|
||||
GLOBAL_TAGS_ASPECT_NAME,
|
||||
(dataProduct, dataMap) ->
|
||||
@ -113,7 +123,10 @@ public class DataProductMapper implements ModelMapper<EntityResponse, DataProduc
|
||||
}
|
||||
|
||||
private void mapDataProductProperties(
|
||||
@Nonnull DataProduct dataProduct, @Nonnull DataMap dataMap, @Nonnull Urn urn) {
|
||||
@Nonnull DataProduct dataProduct,
|
||||
@Nonnull DataMap dataMap,
|
||||
@Nonnull Urn urn,
|
||||
final ResolvedAuditStamp createdAuditStamp) {
|
||||
DataProductProperties dataProductProperties = new DataProductProperties(dataMap);
|
||||
com.linkedin.datahub.graphql.generated.DataProductProperties properties =
|
||||
new com.linkedin.datahub.graphql.generated.DataProductProperties();
|
||||
@ -134,6 +147,8 @@ public class DataProductMapper implements ModelMapper<EntityResponse, DataProduc
|
||||
CustomPropertiesMapper.map(
|
||||
dataProductProperties.getCustomProperties(), UrnUtils.getUrn(dataProduct.getUrn())));
|
||||
|
||||
properties.setCreatedOn(createdAuditStamp);
|
||||
|
||||
dataProduct.setProperties(properties);
|
||||
}
|
||||
|
||||
|
@ -9,15 +9,19 @@ import com.linkedin.common.Forms;
|
||||
import com.linkedin.common.InstitutionalMemory;
|
||||
import com.linkedin.common.Ownership;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.data.template.GetMode;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||
import com.linkedin.datahub.graphql.generated.CorpUser;
|
||||
import com.linkedin.datahub.graphql.generated.Domain;
|
||||
import com.linkedin.datahub.graphql.generated.EntityType;
|
||||
import com.linkedin.datahub.graphql.generated.ResolvedAuditStamp;
|
||||
import com.linkedin.datahub.graphql.types.common.mappers.DisplayPropertiesMapper;
|
||||
import com.linkedin.datahub.graphql.types.common.mappers.InstitutionalMemoryMapper;
|
||||
import com.linkedin.datahub.graphql.types.common.mappers.OwnershipMapper;
|
||||
import com.linkedin.datahub.graphql.types.form.FormsMapper;
|
||||
import com.linkedin.datahub.graphql.types.structuredproperty.StructuredPropertiesMapper;
|
||||
import com.linkedin.datahub.graphql.util.EntityResponseUtils;
|
||||
import com.linkedin.domain.DomainProperties;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.EnvelopedAspect;
|
||||
@ -34,6 +38,11 @@ public class DomainMapper {
|
||||
final Urn entityUrn = entityResponse.getUrn();
|
||||
final EnvelopedAspectMap aspects = entityResponse.getAspects();
|
||||
|
||||
// Getting of created timestamp from key aspect as we can't get this data in default way
|
||||
ResolvedAuditStamp createdAuditStampFromKeyAspect =
|
||||
EntityResponseUtils.extractAspectCreatedAuditStamp(
|
||||
entityResponse, Constants.DOMAIN_KEY_ASPECT_NAME);
|
||||
|
||||
result.setUrn(entityUrn.toString());
|
||||
result.setType(EntityType.DOMAIN);
|
||||
|
||||
@ -49,7 +58,9 @@ public class DomainMapper {
|
||||
aspects.get(Constants.DOMAIN_PROPERTIES_ASPECT_NAME);
|
||||
if (envelopedDomainProperties != null) {
|
||||
result.setProperties(
|
||||
mapDomainProperties(new DomainProperties(envelopedDomainProperties.getValue().data())));
|
||||
mapDomainProperties(
|
||||
new DomainProperties(envelopedDomainProperties.getValue().data()),
|
||||
createdAuditStampFromKeyAspect));
|
||||
}
|
||||
|
||||
final EnvelopedAspect envelopedOwnership = aspects.get(Constants.OWNERSHIP_ASPECT_NAME);
|
||||
@ -100,11 +111,28 @@ public class DomainMapper {
|
||||
}
|
||||
|
||||
private static com.linkedin.datahub.graphql.generated.DomainProperties mapDomainProperties(
|
||||
final DomainProperties gmsProperties) {
|
||||
final DomainProperties gmsProperties,
|
||||
final ResolvedAuditStamp createdAuditStampFromKeyAspect) {
|
||||
final com.linkedin.datahub.graphql.generated.DomainProperties propertiesResult =
|
||||
new com.linkedin.datahub.graphql.generated.DomainProperties();
|
||||
propertiesResult.setName(gmsProperties.getName());
|
||||
propertiesResult.setDescription(gmsProperties.getDescription());
|
||||
|
||||
// Map created audit stamp
|
||||
if (gmsProperties.getCreated() != null) {
|
||||
ResolvedAuditStamp created = new ResolvedAuditStamp();
|
||||
created.setTime(gmsProperties.getCreated().getTime());
|
||||
if (gmsProperties.getCreated().getActor(GetMode.NULL) != null) {
|
||||
final CorpUser emptyCreatedUser = new CorpUser();
|
||||
emptyCreatedUser.setUrn(gmsProperties.getCreated().getActor().toString());
|
||||
created.setActor(emptyCreatedUser);
|
||||
}
|
||||
propertiesResult.setCreatedOn(created);
|
||||
} else {
|
||||
// FYI: sometimes it's empty in data so we have fallback to audit stamp from key aspect
|
||||
propertiesResult.setCreatedOn(createdAuditStampFromKeyAspect);
|
||||
}
|
||||
|
||||
return propertiesResult;
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||
import com.linkedin.datahub.graphql.generated.EntityType;
|
||||
import com.linkedin.datahub.graphql.generated.GlossaryNode;
|
||||
import com.linkedin.datahub.graphql.generated.GlossaryNodeProperties;
|
||||
import com.linkedin.datahub.graphql.generated.ResolvedAuditStamp;
|
||||
import com.linkedin.datahub.graphql.types.common.mappers.CustomPropertiesMapper;
|
||||
import com.linkedin.datahub.graphql.types.common.mappers.DisplayPropertiesMapper;
|
||||
import com.linkedin.datahub.graphql.types.common.mappers.OwnershipMapper;
|
||||
@ -20,6 +21,7 @@ import com.linkedin.datahub.graphql.types.common.mappers.util.MappingHelper;
|
||||
import com.linkedin.datahub.graphql.types.form.FormsMapper;
|
||||
import com.linkedin.datahub.graphql.types.mappers.ModelMapper;
|
||||
import com.linkedin.datahub.graphql.types.structuredproperty.StructuredPropertiesMapper;
|
||||
import com.linkedin.datahub.graphql.util.EntityResponseUtils;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.EnvelopedAspectMap;
|
||||
import com.linkedin.glossary.GlossaryNodeInfo;
|
||||
@ -45,12 +47,18 @@ public class GlossaryNodeMapper implements ModelMapper<EntityResponse, GlossaryN
|
||||
result.setType(EntityType.GLOSSARY_NODE);
|
||||
Urn entityUrn = entityResponse.getUrn();
|
||||
|
||||
// Getting of created timestamp from key aspect as we can't get this data in default way
|
||||
ResolvedAuditStamp createdAuditStampFromKeyAspect =
|
||||
EntityResponseUtils.extractAspectCreatedAuditStamp(
|
||||
entityResponse, GLOSSARY_NODE_KEY_ASPECT_NAME);
|
||||
|
||||
EnvelopedAspectMap aspectMap = entityResponse.getAspects();
|
||||
MappingHelper<GlossaryNode> mappingHelper = new MappingHelper<>(aspectMap, result);
|
||||
mappingHelper.mapToResult(
|
||||
GLOSSARY_NODE_INFO_ASPECT_NAME,
|
||||
(glossaryNode, dataMap) ->
|
||||
glossaryNode.setProperties(mapGlossaryNodeProperties(dataMap, entityUrn)));
|
||||
glossaryNode.setProperties(
|
||||
mapGlossaryNodeProperties(dataMap, entityUrn, createdAuditStampFromKeyAspect)));
|
||||
mappingHelper.mapToResult(GLOSSARY_NODE_KEY_ASPECT_NAME, this::mapGlossaryNodeKey);
|
||||
mappingHelper.mapToResult(
|
||||
OWNERSHIP_ASPECT_NAME,
|
||||
@ -81,7 +89,9 @@ public class GlossaryNodeMapper implements ModelMapper<EntityResponse, GlossaryN
|
||||
}
|
||||
|
||||
private GlossaryNodeProperties mapGlossaryNodeProperties(
|
||||
@Nonnull DataMap dataMap, @Nonnull final Urn entityUrn) {
|
||||
@Nonnull DataMap dataMap,
|
||||
@Nonnull final Urn entityUrn,
|
||||
final ResolvedAuditStamp createdAuditStamp) {
|
||||
GlossaryNodeInfo glossaryNodeInfo = new GlossaryNodeInfo(dataMap);
|
||||
GlossaryNodeProperties result = new GlossaryNodeProperties();
|
||||
result.setDescription(glossaryNodeInfo.getDefinition());
|
||||
@ -92,6 +102,7 @@ public class GlossaryNodeMapper implements ModelMapper<EntityResponse, GlossaryN
|
||||
result.setCustomProperties(
|
||||
CustomPropertiesMapper.map(glossaryNodeInfo.getCustomProperties(), entityUrn));
|
||||
}
|
||||
result.setCreatedOn(createdAuditStamp);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -15,6 +15,7 @@ import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||
import com.linkedin.datahub.graphql.generated.EntityType;
|
||||
import com.linkedin.datahub.graphql.generated.GlossaryTerm;
|
||||
import com.linkedin.datahub.graphql.generated.ResolvedAuditStamp;
|
||||
import com.linkedin.datahub.graphql.types.application.ApplicationAssociationMapper;
|
||||
import com.linkedin.datahub.graphql.types.common.mappers.DeprecationMapper;
|
||||
import com.linkedin.datahub.graphql.types.common.mappers.InstitutionalMemoryMapper;
|
||||
@ -25,6 +26,7 @@ import com.linkedin.datahub.graphql.types.form.FormsMapper;
|
||||
import com.linkedin.datahub.graphql.types.glossary.GlossaryTermUtils;
|
||||
import com.linkedin.datahub.graphql.types.mappers.ModelMapper;
|
||||
import com.linkedin.datahub.graphql.types.structuredproperty.StructuredPropertiesMapper;
|
||||
import com.linkedin.datahub.graphql.util.EntityResponseUtils;
|
||||
import com.linkedin.domain.Domains;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.EnvelopedAspectMap;
|
||||
@ -59,6 +61,11 @@ public class GlossaryTermMapper implements ModelMapper<EntityResponse, GlossaryT
|
||||
final String legacyName =
|
||||
GlossaryTermUtils.getGlossaryTermName(entityResponse.getUrn().getId());
|
||||
|
||||
// Getting of created timestamp from key aspect as we can't get this data in default way
|
||||
ResolvedAuditStamp createdAuditStampFromKeyAspect =
|
||||
EntityResponseUtils.extractAspectCreatedAuditStamp(
|
||||
entityResponse, GLOSSARY_TERM_KEY_ASPECT_NAME);
|
||||
|
||||
EnvelopedAspectMap aspectMap = entityResponse.getAspects();
|
||||
MappingHelper<GlossaryTerm> mappingHelper = new MappingHelper<>(aspectMap, result);
|
||||
mappingHelper.mapToResult(GLOSSARY_TERM_KEY_ASPECT_NAME, this::mapGlossaryTermKey);
|
||||
@ -71,7 +78,8 @@ public class GlossaryTermMapper implements ModelMapper<EntityResponse, GlossaryT
|
||||
GLOSSARY_TERM_INFO_ASPECT_NAME,
|
||||
(glossaryTerm, dataMap) ->
|
||||
glossaryTerm.setProperties(
|
||||
GlossaryTermPropertiesMapper.map(new GlossaryTermInfo(dataMap), entityUrn)));
|
||||
GlossaryTermPropertiesMapper.map(
|
||||
new GlossaryTermInfo(dataMap), entityUrn, createdAuditStampFromKeyAspect)));
|
||||
mappingHelper.mapToResult(
|
||||
OWNERSHIP_ASPECT_NAME,
|
||||
(glossaryTerm, dataMap) ->
|
||||
|
@ -2,6 +2,7 @@ package com.linkedin.datahub.graphql.types.glossary.mappers;
|
||||
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.datahub.graphql.generated.GlossaryTermProperties;
|
||||
import com.linkedin.datahub.graphql.generated.ResolvedAuditStamp;
|
||||
import com.linkedin.datahub.graphql.types.common.mappers.CustomPropertiesMapper;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
@ -15,12 +16,16 @@ public class GlossaryTermPropertiesMapper {
|
||||
public static final GlossaryTermPropertiesMapper INSTANCE = new GlossaryTermPropertiesMapper();
|
||||
|
||||
public static GlossaryTermProperties map(
|
||||
@Nonnull final com.linkedin.glossary.GlossaryTermInfo glossaryTermInfo, Urn entityUrn) {
|
||||
return INSTANCE.apply(glossaryTermInfo, entityUrn);
|
||||
@Nonnull final com.linkedin.glossary.GlossaryTermInfo glossaryTermInfo,
|
||||
Urn entityUrn,
|
||||
final ResolvedAuditStamp createdAuditStamp) {
|
||||
return INSTANCE.apply(glossaryTermInfo, entityUrn, createdAuditStamp);
|
||||
}
|
||||
|
||||
public GlossaryTermProperties apply(
|
||||
@Nonnull final com.linkedin.glossary.GlossaryTermInfo glossaryTermInfo, Urn entityUrn) {
|
||||
@Nonnull final com.linkedin.glossary.GlossaryTermInfo glossaryTermInfo,
|
||||
Urn entityUrn,
|
||||
final ResolvedAuditStamp createdAuditStamp) {
|
||||
com.linkedin.datahub.graphql.generated.GlossaryTermProperties result =
|
||||
new com.linkedin.datahub.graphql.generated.GlossaryTermProperties();
|
||||
result.setDefinition(glossaryTermInfo.getDefinition());
|
||||
@ -39,6 +44,7 @@ public class GlossaryTermPropertiesMapper {
|
||||
result.setCustomProperties(
|
||||
CustomPropertiesMapper.map(glossaryTermInfo.getCustomProperties(), entityUrn));
|
||||
}
|
||||
result.setCreatedOn(createdAuditStamp);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
package com.linkedin.datahub.graphql.util;
|
||||
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.datahub.graphql.generated.CorpUser;
|
||||
import com.linkedin.datahub.graphql.generated.ResolvedAuditStamp;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.EnvelopedAspect;
|
||||
import com.linkedin.entity.EnvelopedAspectMap;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class EntityResponseUtils {
|
||||
|
||||
private EntityResponseUtils() {}
|
||||
|
||||
@Nullable
|
||||
public static ResolvedAuditStamp extractAspectCreatedAuditStamp(
|
||||
final EntityResponse entityResponse, @Nonnull final String aspectName) {
|
||||
|
||||
if (entityResponse == null) {
|
||||
log.warn(
|
||||
"Can't get created audit stamp for null entityResponse by aspectName {}", aspectName);
|
||||
return null;
|
||||
}
|
||||
|
||||
Urn entityUrn = entityResponse.getUrn();
|
||||
|
||||
if (!entityResponse.hasAspects()) {
|
||||
log.warn(
|
||||
"Can't get created audit stamp from entityResponse without aspects by aspectName {}. urn: {}",
|
||||
aspectName,
|
||||
entityUrn);
|
||||
return null;
|
||||
}
|
||||
|
||||
EnvelopedAspectMap aspectMap = entityResponse.getAspects();
|
||||
|
||||
if (!aspectMap.containsKey(aspectName)) {
|
||||
log.warn(
|
||||
"Can't get created audit stamp from entityResponse by aspectName {} as it doesn't contain this aspect. Urn: {}",
|
||||
aspectName,
|
||||
entityUrn);
|
||||
return null;
|
||||
}
|
||||
|
||||
EnvelopedAspect aspect = aspectMap.get(aspectName);
|
||||
|
||||
if (aspect == null) {
|
||||
log.warn(
|
||||
"Can't get created audit stamp from entityResponse by aspectName {} as this aspect is null. Urn: {}",
|
||||
aspectName,
|
||||
entityUrn);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!aspect.hasCreated()) {
|
||||
log.warn(
|
||||
"Can't get created audit stamp from entityResponse as {} aspect doesn't have created audit stamp. Urn: {}",
|
||||
aspectName,
|
||||
entityUrn);
|
||||
return null;
|
||||
}
|
||||
|
||||
ResolvedAuditStamp auditStamp = new ResolvedAuditStamp();
|
||||
final CorpUser emptyCreatedUser = new CorpUser();
|
||||
emptyCreatedUser.setUrn(aspect.getCreated().getActor().toString());
|
||||
auditStamp.setActor(emptyCreatedUser);
|
||||
auditStamp.setTime(aspect.getCreated().getTime());
|
||||
return auditStamp;
|
||||
}
|
||||
}
|
@ -2597,6 +2597,11 @@ type GlossaryTermProperties {
|
||||
Schema definition of glossary term
|
||||
"""
|
||||
rawSchema: String
|
||||
|
||||
"""
|
||||
A Resolved Audit Stamp corresponding to the creation of this resource
|
||||
"""
|
||||
createdOn: ResolvedAuditStamp
|
||||
}
|
||||
|
||||
"""
|
||||
@ -2722,6 +2727,11 @@ type GlossaryNodeProperties {
|
||||
Custom properties of the Glossary Node
|
||||
"""
|
||||
customProperties: [CustomPropertiesEntry!]
|
||||
|
||||
"""
|
||||
A Resolved Audit Stamp corresponding to the creation of this resource
|
||||
"""
|
||||
createdOn: ResolvedAuditStamp
|
||||
}
|
||||
|
||||
"""
|
||||
@ -11492,6 +11502,11 @@ type DomainProperties {
|
||||
Description of the Domain
|
||||
"""
|
||||
description: String
|
||||
|
||||
"""
|
||||
A Resolved Audit Stamp corresponding to the creation of this resource
|
||||
"""
|
||||
createdOn: ResolvedAuditStamp
|
||||
}
|
||||
|
||||
"""
|
||||
@ -13062,6 +13077,11 @@ type DataProductProperties {
|
||||
Custom properties of the Data Product
|
||||
"""
|
||||
customProperties: [CustomPropertiesEntry!]
|
||||
|
||||
"""
|
||||
A Resolved Audit Stamp corresponding to the creation of this resource
|
||||
"""
|
||||
createdOn: ResolvedAuditStamp
|
||||
}
|
||||
|
||||
"""
|
||||
|
@ -0,0 +1,479 @@
|
||||
package com.linkedin.datahub.graphql.types.dataproduct.mappers;
|
||||
|
||||
import static com.linkedin.metadata.Constants.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
import com.linkedin.application.Applications;
|
||||
import com.linkedin.common.AuditStamp;
|
||||
import com.linkedin.common.Forms;
|
||||
import com.linkedin.common.GlobalTags;
|
||||
import com.linkedin.common.GlossaryTerms;
|
||||
import com.linkedin.common.InstitutionalMemory;
|
||||
import com.linkedin.common.Ownership;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||
import com.linkedin.datahub.graphql.generated.DataProduct;
|
||||
import com.linkedin.datahub.graphql.generated.EntityType;
|
||||
import com.linkedin.dataproduct.DataProductKey;
|
||||
import com.linkedin.dataproduct.DataProductProperties;
|
||||
import com.linkedin.domain.Domains;
|
||||
import com.linkedin.entity.Aspect;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.EnvelopedAspect;
|
||||
import com.linkedin.entity.EnvelopedAspectMap;
|
||||
import com.linkedin.structured.StructuredProperties;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class DataProductMapperTest {
|
||||
|
||||
private static final String TEST_DATA_PRODUCT_URN = "urn:li:dataProduct:customer_analytics";
|
||||
private static final String TEST_DATA_PRODUCT_ID = "customer_analytics";
|
||||
private static final String TEST_DATA_PRODUCT_NAME = "Customer Analytics Data Product";
|
||||
private static final String TEST_DATA_PRODUCT_DESCRIPTION =
|
||||
"Analytics data product for customer insights";
|
||||
private static final String TEST_EXTERNAL_URL = "https://example.com/data-product";
|
||||
private static final String TEST_ACTOR_URN = "urn:li:corpuser:testuser";
|
||||
private static final Long TEST_TIMESTAMP = 1640995200000L; // 2022-01-01 00:00:00 UTC
|
||||
|
||||
private Urn dataProductUrn;
|
||||
private Urn actorUrn;
|
||||
private QueryContext mockQueryContext;
|
||||
|
||||
@BeforeMethod
|
||||
public void setup() throws URISyntaxException {
|
||||
dataProductUrn = Urn.createFromString(TEST_DATA_PRODUCT_URN);
|
||||
actorUrn = Urn.createFromString(TEST_ACTOR_URN);
|
||||
mockQueryContext = mock(QueryContext.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDataProductWithAllAspects() throws URISyntaxException {
|
||||
// Setup entity response with all aspects
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Add data product properties
|
||||
DataProductProperties dataProductProperties = new DataProductProperties();
|
||||
dataProductProperties.setName(TEST_DATA_PRODUCT_NAME);
|
||||
dataProductProperties.setDescription(TEST_DATA_PRODUCT_DESCRIPTION);
|
||||
// Skip external URL test for now due to Uri constructor issues
|
||||
// dataProductProperties.setExternalUrl(new Uri(TEST_EXTERNAL_URL));
|
||||
|
||||
// Add some assets to test numAssets
|
||||
com.linkedin.dataproduct.DataProductAssociationArray assets =
|
||||
new com.linkedin.dataproduct.DataProductAssociationArray();
|
||||
com.linkedin.dataproduct.DataProductAssociation asset1 =
|
||||
new com.linkedin.dataproduct.DataProductAssociation();
|
||||
asset1.setDestinationUrn(
|
||||
Urn.createFromString("urn:li:dataset:(urn:li:dataPlatform:snowflake,table1,PROD)"));
|
||||
assets.add(asset1);
|
||||
com.linkedin.dataproduct.DataProductAssociation asset2 =
|
||||
new com.linkedin.dataproduct.DataProductAssociation();
|
||||
asset2.setDestinationUrn(
|
||||
Urn.createFromString("urn:li:dataset:(urn:li:dataPlatform:snowflake,table2,PROD)"));
|
||||
assets.add(asset2);
|
||||
dataProductProperties.setAssets(assets);
|
||||
|
||||
addAspectToResponse(entityResponse, DATA_PRODUCT_PROPERTIES_ASPECT_NAME, dataProductProperties);
|
||||
|
||||
// Add ownership
|
||||
Ownership ownership = new Ownership();
|
||||
ownership.setOwners(new com.linkedin.common.OwnerArray());
|
||||
addAspectToResponse(entityResponse, OWNERSHIP_ASPECT_NAME, ownership);
|
||||
|
||||
// Add institutional memory
|
||||
InstitutionalMemory institutionalMemory = new InstitutionalMemory();
|
||||
institutionalMemory.setElements(new com.linkedin.common.InstitutionalMemoryMetadataArray());
|
||||
addAspectToResponse(entityResponse, INSTITUTIONAL_MEMORY_ASPECT_NAME, institutionalMemory);
|
||||
|
||||
// Add structured properties
|
||||
StructuredProperties structuredProperties = new StructuredProperties();
|
||||
structuredProperties.setProperties(
|
||||
new com.linkedin.structured.StructuredPropertyValueAssignmentArray());
|
||||
addAspectToResponse(entityResponse, STRUCTURED_PROPERTIES_ASPECT_NAME, structuredProperties);
|
||||
|
||||
// Add forms
|
||||
Forms forms = new Forms();
|
||||
forms.setIncompleteForms(new com.linkedin.common.FormAssociationArray());
|
||||
forms.setCompletedForms(new com.linkedin.common.FormAssociationArray());
|
||||
addAspectToResponse(entityResponse, FORMS_ASPECT_NAME, forms);
|
||||
|
||||
// Add global tags
|
||||
GlobalTags globalTags = new GlobalTags();
|
||||
globalTags.setTags(new com.linkedin.common.TagAssociationArray());
|
||||
addAspectToResponse(entityResponse, GLOBAL_TAGS_ASPECT_NAME, globalTags);
|
||||
|
||||
// Add glossary terms
|
||||
GlossaryTerms glossaryTerms = new GlossaryTerms();
|
||||
glossaryTerms.setTerms(new com.linkedin.common.GlossaryTermAssociationArray());
|
||||
addAspectToResponse(entityResponse, GLOSSARY_TERMS_ASPECT_NAME, glossaryTerms);
|
||||
|
||||
// Add domains
|
||||
Domains domains = new Domains();
|
||||
domains.setDomains(new com.linkedin.common.UrnArray());
|
||||
addAspectToResponse(entityResponse, DOMAINS_ASPECT_NAME, domains);
|
||||
|
||||
// Add application membership
|
||||
Applications applications = new Applications();
|
||||
applications.setApplications(new com.linkedin.common.UrnArray());
|
||||
addAspectToResponse(entityResponse, APPLICATION_MEMBERSHIP_ASPECT_NAME, applications);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock
|
||||
.when(() -> AuthorizationUtils.canView(any(), eq(dataProductUrn)))
|
||||
.thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
DataProduct result = DataProductMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Verify results
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getUrn(), TEST_DATA_PRODUCT_URN);
|
||||
assertEquals(result.getType(), EntityType.DATA_PRODUCT);
|
||||
|
||||
// Verify data product properties
|
||||
assertNotNull(result.getProperties());
|
||||
assertEquals(result.getProperties().getName(), TEST_DATA_PRODUCT_NAME);
|
||||
assertEquals(result.getProperties().getDescription(), TEST_DATA_PRODUCT_DESCRIPTION);
|
||||
// assertEquals(result.getProperties().getExternalUrl(), TEST_EXTERNAL_URL); // Skipped due to
|
||||
// Uri issue
|
||||
assertEquals(result.getProperties().getNumAssets(), Integer.valueOf(2)); // Two assets added
|
||||
|
||||
// Verify created audit stamp (may be null if key aspect doesn't have created timestamp)
|
||||
// This is expected since we didn't add a created audit stamp to the key aspect
|
||||
assertNull(result.getProperties().getCreatedOn());
|
||||
|
||||
// Verify other aspects are set
|
||||
assertNotNull(result.getOwnership());
|
||||
assertNotNull(result.getInstitutionalMemory());
|
||||
assertNotNull(result.getStructuredProperties());
|
||||
assertNotNull(result.getForms());
|
||||
assertNotNull(result.getTags());
|
||||
assertNotNull(result.getGlossaryTerms());
|
||||
// Domain association might be null if DomainAssociationMapper returns null
|
||||
// assertNotNull(result.getDomain());
|
||||
// Application association might be null if ApplicationAssociationMapper returns null
|
||||
// assertNotNull(result.getApplication());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDataProductWithMinimalAspects() {
|
||||
// Setup entity response with only key aspect
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock
|
||||
.when(() -> AuthorizationUtils.canView(any(), eq(dataProductUrn)))
|
||||
.thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
DataProduct result = DataProductMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Verify results
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getUrn(), TEST_DATA_PRODUCT_URN);
|
||||
assertEquals(result.getType(), EntityType.DATA_PRODUCT);
|
||||
|
||||
// Verify optional aspects are null
|
||||
assertNull(result.getProperties());
|
||||
assertNull(result.getOwnership());
|
||||
assertNull(result.getInstitutionalMemory());
|
||||
assertNull(result.getStructuredProperties());
|
||||
assertNull(result.getForms());
|
||||
assertNull(result.getTags());
|
||||
assertNull(result.getGlossaryTerms());
|
||||
assertNull(result.getDomain());
|
||||
assertNull(result.getApplication());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDataProductWithCreatedAuditStampFromKeyAspect() throws URISyntaxException {
|
||||
// Setup entity response with data product key that has created audit stamp
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Add data product properties
|
||||
DataProductProperties dataProductProperties = new DataProductProperties();
|
||||
dataProductProperties.setName(TEST_DATA_PRODUCT_NAME);
|
||||
dataProductProperties.setDescription(TEST_DATA_PRODUCT_DESCRIPTION);
|
||||
|
||||
addAspectToResponse(entityResponse, DATA_PRODUCT_PROPERTIES_ASPECT_NAME, dataProductProperties);
|
||||
|
||||
// Add created audit stamp to the data product key aspect
|
||||
EnvelopedAspect dataProductKeyAspect =
|
||||
entityResponse.getAspects().get(DATA_PRODUCT_KEY_ASPECT_NAME);
|
||||
AuditStamp keyCreatedStamp = new AuditStamp();
|
||||
keyCreatedStamp.setTime(TEST_TIMESTAMP);
|
||||
keyCreatedStamp.setActor(actorUrn);
|
||||
dataProductKeyAspect.setCreated(keyCreatedStamp);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock
|
||||
.when(() -> AuthorizationUtils.canView(any(), eq(dataProductUrn)))
|
||||
.thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
DataProduct result = DataProductMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Verify results
|
||||
assertNotNull(result);
|
||||
assertNotNull(result.getProperties());
|
||||
assertEquals(result.getProperties().getName(), TEST_DATA_PRODUCT_NAME);
|
||||
assertEquals(result.getProperties().getDescription(), TEST_DATA_PRODUCT_DESCRIPTION);
|
||||
|
||||
// Verify created audit stamp is extracted from key aspect
|
||||
assertNotNull(result.getProperties().getCreatedOn());
|
||||
assertEquals(result.getProperties().getCreatedOn().getTime(), TEST_TIMESTAMP);
|
||||
assertEquals(result.getProperties().getCreatedOn().getActor().getUrn(), TEST_ACTOR_URN);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDataProductPropertiesWithNameFallback() {
|
||||
// Setup entity response
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Add data product properties without explicit name (should fallback to URN ID)
|
||||
DataProductProperties dataProductProperties = new DataProductProperties();
|
||||
dataProductProperties.setDescription(TEST_DATA_PRODUCT_DESCRIPTION);
|
||||
// Note: NOT setting name to test URN ID fallback
|
||||
|
||||
addAspectToResponse(entityResponse, DATA_PRODUCT_PROPERTIES_ASPECT_NAME, dataProductProperties);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock
|
||||
.when(() -> AuthorizationUtils.canView(any(), eq(dataProductUrn)))
|
||||
.thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
DataProduct result = DataProductMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Verify name fallback to URN ID
|
||||
assertNotNull(result.getProperties());
|
||||
assertEquals(result.getProperties().getName(), TEST_DATA_PRODUCT_ID); // Should use URN ID
|
||||
assertEquals(result.getProperties().getDescription(), TEST_DATA_PRODUCT_DESCRIPTION);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDataProductPropertiesWithNoAssets() {
|
||||
// Setup entity response
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Add data product properties without assets
|
||||
DataProductProperties dataProductProperties = new DataProductProperties();
|
||||
dataProductProperties.setName(TEST_DATA_PRODUCT_NAME);
|
||||
dataProductProperties.setDescription(TEST_DATA_PRODUCT_DESCRIPTION);
|
||||
// Note: NOT setting assets to test numAssets = 0
|
||||
|
||||
addAspectToResponse(entityResponse, DATA_PRODUCT_PROPERTIES_ASPECT_NAME, dataProductProperties);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock
|
||||
.when(() -> AuthorizationUtils.canView(any(), eq(dataProductUrn)))
|
||||
.thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
DataProduct result = DataProductMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Verify numAssets is 0 when no assets are present
|
||||
assertNotNull(result.getProperties());
|
||||
assertEquals(result.getProperties().getNumAssets(), Integer.valueOf(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDataProductWithRestrictedAccess() {
|
||||
// Setup entity response
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Mock authorization to deny access
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock
|
||||
.when(() -> AuthorizationUtils.canView(any(), eq(dataProductUrn)))
|
||||
.thenReturn(false);
|
||||
|
||||
DataProduct restrictedDataProduct = new DataProduct();
|
||||
authUtilsMock
|
||||
.when(
|
||||
() ->
|
||||
AuthorizationUtils.restrictEntity(any(DataProduct.class), eq(DataProduct.class)))
|
||||
.thenReturn(restrictedDataProduct);
|
||||
|
||||
// Execute mapping
|
||||
DataProduct result = DataProductMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Should return restricted entity
|
||||
assertEquals(result, restrictedDataProduct);
|
||||
|
||||
// Verify authorization calls
|
||||
authUtilsMock.verify(() -> AuthorizationUtils.canView(any(), eq(dataProductUrn)));
|
||||
authUtilsMock.verify(
|
||||
() -> AuthorizationUtils.restrictEntity(any(DataProduct.class), eq(DataProduct.class)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDataProductWithNullQueryContext() {
|
||||
// Setup entity response
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Execute mapping with null query context
|
||||
DataProduct result = DataProductMapper.map(null, entityResponse);
|
||||
|
||||
// Should return data product without authorization checks
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getUrn(), TEST_DATA_PRODUCT_URN);
|
||||
assertEquals(result.getType(), EntityType.DATA_PRODUCT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDataProductWithCustomProperties() throws URISyntaxException {
|
||||
// Setup entity response
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Add data product properties with custom properties
|
||||
DataProductProperties dataProductProperties = new DataProductProperties();
|
||||
dataProductProperties.setName(TEST_DATA_PRODUCT_NAME);
|
||||
|
||||
// Add custom properties
|
||||
com.linkedin.data.template.StringMap customProperties =
|
||||
new com.linkedin.data.template.StringMap();
|
||||
customProperties.put("environment", "production");
|
||||
customProperties.put("team", "data-platform");
|
||||
dataProductProperties.setCustomProperties(customProperties);
|
||||
|
||||
addAspectToResponse(entityResponse, DATA_PRODUCT_PROPERTIES_ASPECT_NAME, dataProductProperties);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock
|
||||
.when(() -> AuthorizationUtils.canView(any(), eq(dataProductUrn)))
|
||||
.thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
DataProduct result = DataProductMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Verify custom properties are mapped
|
||||
assertNotNull(result.getProperties());
|
||||
assertNotNull(result.getProperties().getCustomProperties());
|
||||
assertEquals(result.getProperties().getCustomProperties().size(), 2);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDataProductUsingStaticMethod() {
|
||||
// Setup entity response
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock
|
||||
.when(() -> AuthorizationUtils.canView(any(), eq(dataProductUrn)))
|
||||
.thenReturn(true);
|
||||
|
||||
// Execute mapping using static method
|
||||
DataProduct result = DataProductMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Should work the same as instance method
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getUrn(), TEST_DATA_PRODUCT_URN);
|
||||
assertEquals(result.getType(), EntityType.DATA_PRODUCT);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDataProductWithApplicationMembership() throws URISyntaxException {
|
||||
// Setup entity response
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Add application membership
|
||||
Applications applications = new Applications();
|
||||
com.linkedin.common.UrnArray applicationUrns = new com.linkedin.common.UrnArray();
|
||||
applicationUrns.add(Urn.createFromString("urn:li:application:test-app"));
|
||||
applications.setApplications(applicationUrns);
|
||||
addAspectToResponse(entityResponse, APPLICATION_MEMBERSHIP_ASPECT_NAME, applications);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock
|
||||
.when(() -> AuthorizationUtils.canView(any(), eq(dataProductUrn)))
|
||||
.thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
DataProduct result = DataProductMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Verify application is mapped (may be null if ApplicationAssociationMapper returns null)
|
||||
assertNotNull(result);
|
||||
// Application association might be null if mapping fails or returns null
|
||||
// assertNotNull(result.getApplication());
|
||||
// For now, let's just verify the result is not null since we don't know the exact behavior
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDataProductWithNoCreatedAuditStampFromKeyAspect() {
|
||||
// Setup entity response without created audit stamp in key aspect
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Add data product properties
|
||||
DataProductProperties dataProductProperties = new DataProductProperties();
|
||||
dataProductProperties.setName(TEST_DATA_PRODUCT_NAME);
|
||||
addAspectToResponse(entityResponse, DATA_PRODUCT_PROPERTIES_ASPECT_NAME, dataProductProperties);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock
|
||||
.when(() -> AuthorizationUtils.canView(any(), eq(dataProductUrn)))
|
||||
.thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
DataProduct result = DataProductMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Should handle null fallback gracefully
|
||||
assertNotNull(result);
|
||||
assertNotNull(result.getProperties());
|
||||
assertEquals(result.getProperties().getName(), TEST_DATA_PRODUCT_NAME);
|
||||
assertNull(result.getProperties().getCreatedOn()); // Should be null when fallback is null
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
private EntityResponse createBasicEntityResponse() {
|
||||
EntityResponse entityResponse = new EntityResponse();
|
||||
entityResponse.setUrn(dataProductUrn);
|
||||
|
||||
// Create data product key aspect
|
||||
DataProductKey dataProductKey = new DataProductKey();
|
||||
dataProductKey.setId(TEST_DATA_PRODUCT_ID);
|
||||
|
||||
EnvelopedAspect dataProductKeyAspect = new EnvelopedAspect();
|
||||
dataProductKeyAspect.setValue(new Aspect(dataProductKey.data()));
|
||||
|
||||
Map<String, EnvelopedAspect> aspects = new HashMap<>();
|
||||
aspects.put(DATA_PRODUCT_KEY_ASPECT_NAME, dataProductKeyAspect);
|
||||
|
||||
entityResponse.setAspects(new EnvelopedAspectMap(aspects));
|
||||
return entityResponse;
|
||||
}
|
||||
|
||||
private void addAspectToResponse(
|
||||
EntityResponse entityResponse, String aspectName, Object aspectData) {
|
||||
EnvelopedAspect aspect = new EnvelopedAspect();
|
||||
aspect.setValue(new Aspect(((com.linkedin.data.template.RecordTemplate) aspectData).data()));
|
||||
entityResponse.getAspects().put(aspectName, aspect);
|
||||
}
|
||||
}
|
@ -0,0 +1,419 @@
|
||||
package com.linkedin.datahub.graphql.types.domain;
|
||||
|
||||
import static com.linkedin.metadata.Constants.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
import com.linkedin.common.AuditStamp;
|
||||
import com.linkedin.common.DisplayProperties;
|
||||
import com.linkedin.common.Forms;
|
||||
import com.linkedin.common.InstitutionalMemory;
|
||||
import com.linkedin.common.Ownership;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.datahub.graphql.QueryContext;
|
||||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
|
||||
import com.linkedin.datahub.graphql.generated.Domain;
|
||||
import com.linkedin.datahub.graphql.generated.EntityType;
|
||||
import com.linkedin.domain.DomainProperties;
|
||||
import com.linkedin.entity.Aspect;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.EnvelopedAspect;
|
||||
import com.linkedin.entity.EnvelopedAspectMap;
|
||||
import com.linkedin.metadata.key.DomainKey;
|
||||
import com.linkedin.structured.StructuredProperties;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.testng.annotations.BeforeMethod;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class DomainMapperTest {
|
||||
|
||||
private static final String TEST_DOMAIN_URN = "urn:li:domain:marketing";
|
||||
private static final String TEST_DOMAIN_ID = "marketing";
|
||||
private static final String TEST_DOMAIN_NAME = "Marketing Domain";
|
||||
private static final String TEST_DOMAIN_DESCRIPTION = "Domain for marketing datasets";
|
||||
private static final String TEST_ACTOR_URN = "urn:li:corpuser:testuser";
|
||||
private static final Long TEST_TIMESTAMP = 1640995200000L; // 2022-01-01 00:00:00 UTC
|
||||
|
||||
private Urn domainUrn;
|
||||
private Urn actorUrn;
|
||||
private QueryContext mockQueryContext;
|
||||
|
||||
@BeforeMethod
|
||||
public void setup() throws URISyntaxException {
|
||||
domainUrn = Urn.createFromString(TEST_DOMAIN_URN);
|
||||
actorUrn = Urn.createFromString(TEST_ACTOR_URN);
|
||||
mockQueryContext = mock(QueryContext.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDomainWithAllAspects() throws URISyntaxException {
|
||||
// Setup entity response with all aspects
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Add domain properties
|
||||
DomainProperties domainProperties = new DomainProperties();
|
||||
domainProperties.setName(TEST_DOMAIN_NAME);
|
||||
domainProperties.setDescription(TEST_DOMAIN_DESCRIPTION);
|
||||
|
||||
AuditStamp createdAuditStamp = new AuditStamp();
|
||||
createdAuditStamp.setTime(TEST_TIMESTAMP);
|
||||
createdAuditStamp.setActor(actorUrn);
|
||||
domainProperties.setCreated(createdAuditStamp);
|
||||
|
||||
addAspectToResponse(entityResponse, DOMAIN_PROPERTIES_ASPECT_NAME, domainProperties);
|
||||
|
||||
// Add ownership
|
||||
Ownership ownership = new Ownership();
|
||||
ownership.setOwners(new com.linkedin.common.OwnerArray()); // Empty but required field
|
||||
addAspectToResponse(entityResponse, OWNERSHIP_ASPECT_NAME, ownership);
|
||||
|
||||
// Add institutional memory
|
||||
InstitutionalMemory institutionalMemory = new InstitutionalMemory();
|
||||
institutionalMemory.setElements(
|
||||
new com.linkedin.common.InstitutionalMemoryMetadataArray()); // Empty but required field
|
||||
addAspectToResponse(entityResponse, INSTITUTIONAL_MEMORY_ASPECT_NAME, institutionalMemory);
|
||||
|
||||
// Add structured properties
|
||||
StructuredProperties structuredProperties = new StructuredProperties();
|
||||
structuredProperties.setProperties(
|
||||
new com.linkedin.structured
|
||||
.StructuredPropertyValueAssignmentArray()); // Empty but required field
|
||||
addAspectToResponse(entityResponse, STRUCTURED_PROPERTIES_ASPECT_NAME, structuredProperties);
|
||||
|
||||
// Add forms
|
||||
Forms forms = new Forms();
|
||||
forms.setIncompleteForms(
|
||||
new com.linkedin.common.FormAssociationArray()); // Empty but required field
|
||||
forms.setCompletedForms(
|
||||
new com.linkedin.common.FormAssociationArray()); // Empty but required field
|
||||
addAspectToResponse(entityResponse, FORMS_ASPECT_NAME, forms);
|
||||
|
||||
// Add display properties
|
||||
DisplayProperties displayProperties = new DisplayProperties();
|
||||
addAspectToResponse(entityResponse, DISPLAY_PROPERTIES_ASPECT_NAME, displayProperties);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock.when(() -> AuthorizationUtils.canView(any(), eq(domainUrn))).thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
Domain result = DomainMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Verify results
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getUrn(), TEST_DOMAIN_URN);
|
||||
assertEquals(result.getType(), EntityType.DOMAIN);
|
||||
assertEquals(result.getId(), TEST_DOMAIN_ID);
|
||||
|
||||
// Verify domain properties
|
||||
assertNotNull(result.getProperties());
|
||||
assertEquals(result.getProperties().getName(), TEST_DOMAIN_NAME);
|
||||
assertEquals(result.getProperties().getDescription(), TEST_DOMAIN_DESCRIPTION);
|
||||
|
||||
// Verify created audit stamp from properties
|
||||
assertNotNull(result.getProperties().getCreatedOn());
|
||||
assertEquals(result.getProperties().getCreatedOn().getTime(), TEST_TIMESTAMP);
|
||||
assertEquals(result.getProperties().getCreatedOn().getActor().getUrn(), TEST_ACTOR_URN);
|
||||
|
||||
// Verify other aspects are set
|
||||
assertNotNull(result.getOwnership());
|
||||
assertNotNull(result.getInstitutionalMemory());
|
||||
assertNotNull(result.getStructuredProperties());
|
||||
assertNotNull(result.getForms());
|
||||
assertNotNull(result.getDisplayProperties());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDomainWithOnlyKeyAspect() {
|
||||
// Setup entity response with only domain key
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock.when(() -> AuthorizationUtils.canView(any(), eq(domainUrn))).thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
Domain result = DomainMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Verify results
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getUrn(), TEST_DOMAIN_URN);
|
||||
assertEquals(result.getType(), EntityType.DOMAIN);
|
||||
assertEquals(result.getId(), TEST_DOMAIN_ID);
|
||||
|
||||
// Verify optional aspects are null
|
||||
assertNull(result.getProperties());
|
||||
assertNull(result.getOwnership());
|
||||
assertNull(result.getInstitutionalMemory());
|
||||
assertNull(result.getStructuredProperties());
|
||||
assertNull(result.getForms());
|
||||
assertNull(result.getDisplayProperties());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDomainWithCreatedAuditStampFallback() throws URISyntaxException {
|
||||
// Setup entity response with domain key that has created audit stamp
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Add domain properties without created timestamp (to trigger fallback)
|
||||
DomainProperties domainProperties = new DomainProperties();
|
||||
domainProperties.setName(TEST_DOMAIN_NAME);
|
||||
domainProperties.setDescription(TEST_DOMAIN_DESCRIPTION);
|
||||
// Note: NOT setting created timestamp to test fallback
|
||||
|
||||
addAspectToResponse(entityResponse, DOMAIN_PROPERTIES_ASPECT_NAME, domainProperties);
|
||||
|
||||
// Add created audit stamp to the domain key aspect (this is what gets extracted)
|
||||
EnvelopedAspect domainKeyAspect = entityResponse.getAspects().get(DOMAIN_KEY_ASPECT_NAME);
|
||||
AuditStamp keyCreatedStamp = new AuditStamp();
|
||||
keyCreatedStamp.setTime(TEST_TIMESTAMP);
|
||||
keyCreatedStamp.setActor(actorUrn);
|
||||
domainKeyAspect.setCreated(keyCreatedStamp);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock.when(() -> AuthorizationUtils.canView(any(), eq(domainUrn))).thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
Domain result = DomainMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Verify results
|
||||
assertNotNull(result);
|
||||
assertNotNull(result.getProperties());
|
||||
assertEquals(result.getProperties().getName(), TEST_DOMAIN_NAME);
|
||||
assertEquals(result.getProperties().getDescription(), TEST_DOMAIN_DESCRIPTION);
|
||||
|
||||
// Verify fallback created audit stamp is used (from key aspect)
|
||||
assertNotNull(result.getProperties().getCreatedOn());
|
||||
assertEquals(result.getProperties().getCreatedOn().getTime(), TEST_TIMESTAMP);
|
||||
assertEquals(result.getProperties().getCreatedOn().getActor().getUrn(), TEST_ACTOR_URN);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDomainWithPropertiesCreatedTimestampTakesPrecedence()
|
||||
throws URISyntaxException {
|
||||
// Setup entity response
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Add domain properties WITH created timestamp
|
||||
DomainProperties domainProperties = new DomainProperties();
|
||||
domainProperties.setName(TEST_DOMAIN_NAME);
|
||||
domainProperties.setDescription(TEST_DOMAIN_DESCRIPTION);
|
||||
|
||||
Long propertiesTimestamp = TEST_TIMESTAMP + 1000L;
|
||||
AuditStamp createdAuditStamp = new AuditStamp();
|
||||
createdAuditStamp.setTime(propertiesTimestamp);
|
||||
createdAuditStamp.setActor(actorUrn);
|
||||
domainProperties.setCreated(createdAuditStamp);
|
||||
|
||||
addAspectToResponse(entityResponse, DOMAIN_PROPERTIES_ASPECT_NAME, domainProperties);
|
||||
|
||||
// Add created audit stamp to the domain key aspect (should be ignored)
|
||||
EnvelopedAspect domainKeyAspect = entityResponse.getAspects().get(DOMAIN_KEY_ASPECT_NAME);
|
||||
AuditStamp keyCreatedStamp = new AuditStamp();
|
||||
keyCreatedStamp.setTime(TEST_TIMESTAMP); // Different timestamp
|
||||
keyCreatedStamp.setActor(actorUrn);
|
||||
domainKeyAspect.setCreated(keyCreatedStamp);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock.when(() -> AuthorizationUtils.canView(any(), eq(domainUrn))).thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
Domain result = DomainMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Verify that properties created timestamp takes precedence (not the fallback)
|
||||
assertNotNull(result.getProperties().getCreatedOn());
|
||||
assertEquals(result.getProperties().getCreatedOn().getTime(), propertiesTimestamp);
|
||||
assertEquals(result.getProperties().getCreatedOn().getActor().getUrn(), TEST_ACTOR_URN);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDomainWithCreatedTimestampWithoutActor() throws URISyntaxException {
|
||||
// Setup entity response
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Add domain properties with created timestamp but no actor
|
||||
DomainProperties domainProperties = new DomainProperties();
|
||||
domainProperties.setName(TEST_DOMAIN_NAME);
|
||||
|
||||
AuditStamp createdAuditStamp = new AuditStamp();
|
||||
createdAuditStamp.setTime(TEST_TIMESTAMP);
|
||||
// Note: NOT setting actor to test GetMode.NULL handling
|
||||
domainProperties.setCreated(createdAuditStamp);
|
||||
|
||||
addAspectToResponse(entityResponse, DOMAIN_PROPERTIES_ASPECT_NAME, domainProperties);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock.when(() -> AuthorizationUtils.canView(any(), eq(domainUrn))).thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
Domain result = DomainMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Verify created audit stamp is created with time but no actor
|
||||
assertNotNull(result.getProperties().getCreatedOn());
|
||||
assertEquals(result.getProperties().getCreatedOn().getTime(), TEST_TIMESTAMP);
|
||||
assertNull(result.getProperties().getCreatedOn().getActor());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDomainWithoutDomainKey() {
|
||||
// Setup entity response without domain key aspect
|
||||
EntityResponse entityResponse = new EntityResponse();
|
||||
entityResponse.setUrn(domainUrn);
|
||||
entityResponse.setAspects(new EnvelopedAspectMap());
|
||||
|
||||
// Execute mapping
|
||||
Domain result = DomainMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Should return null when domain key aspect is missing
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDomainWithMissingDomainKey() {
|
||||
// Setup entity response with no domain key aspect (empty aspects map)
|
||||
EntityResponse entityResponse = new EntityResponse();
|
||||
entityResponse.setUrn(domainUrn);
|
||||
entityResponse.setAspects(new EnvelopedAspectMap()); // Empty map instead of null value
|
||||
|
||||
// Execute mapping
|
||||
Domain result = DomainMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Should return null when domain key aspect is missing
|
||||
assertNull(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDomainWithRestrictedAccess() {
|
||||
// Setup entity response
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Mock authorization to deny access
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock.when(() -> AuthorizationUtils.canView(any(), eq(domainUrn))).thenReturn(false);
|
||||
|
||||
Domain restrictedDomain = new Domain();
|
||||
authUtilsMock
|
||||
.when(() -> AuthorizationUtils.restrictEntity(any(Domain.class), eq(Domain.class)))
|
||||
.thenReturn(restrictedDomain);
|
||||
|
||||
// Execute mapping
|
||||
Domain result = DomainMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Should return restricted entity
|
||||
assertEquals(result, restrictedDomain);
|
||||
|
||||
// Verify authorization calls
|
||||
authUtilsMock.verify(() -> AuthorizationUtils.canView(any(), eq(domainUrn)));
|
||||
authUtilsMock.verify(
|
||||
() -> AuthorizationUtils.restrictEntity(any(Domain.class), eq(Domain.class)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDomainWithNullQueryContext() {
|
||||
// Setup entity response
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Execute mapping with null query context
|
||||
Domain result = DomainMapper.map(null, entityResponse);
|
||||
|
||||
// Should return domain without authorization checks
|
||||
assertNotNull(result);
|
||||
assertEquals(result.getUrn(), TEST_DOMAIN_URN);
|
||||
assertEquals(result.getType(), EntityType.DOMAIN);
|
||||
assertEquals(result.getId(), TEST_DOMAIN_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDomainWithNoCreatedAuditStampFromKeyAspect() {
|
||||
// Setup entity response without created audit stamp in key aspect
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Test without created audit stamp by not setting one initially in the basic response
|
||||
// Note: createBasicEntityResponse() doesn't set created by default
|
||||
|
||||
// Add domain properties without created timestamp
|
||||
DomainProperties domainProperties = new DomainProperties();
|
||||
domainProperties.setName(TEST_DOMAIN_NAME);
|
||||
addAspectToResponse(entityResponse, DOMAIN_PROPERTIES_ASPECT_NAME, domainProperties);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock.when(() -> AuthorizationUtils.canView(any(), eq(domainUrn))).thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
Domain result = DomainMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Should handle null fallback gracefully
|
||||
assertNotNull(result);
|
||||
assertNotNull(result.getProperties());
|
||||
assertEquals(result.getProperties().getName(), TEST_DOMAIN_NAME);
|
||||
assertNull(result.getProperties().getCreatedOn()); // Should be null when fallback is null
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapDomainWithEmptyDomainProperties() {
|
||||
// Setup entity response
|
||||
EntityResponse entityResponse = createBasicEntityResponse();
|
||||
|
||||
// Add empty domain properties
|
||||
DomainProperties domainProperties = new DomainProperties();
|
||||
domainProperties.setName("Test Domain"); // Required field
|
||||
addAspectToResponse(entityResponse, DOMAIN_PROPERTIES_ASPECT_NAME, domainProperties);
|
||||
|
||||
// Mock authorization
|
||||
try (MockedStatic<AuthorizationUtils> authUtilsMock = mockStatic(AuthorizationUtils.class)) {
|
||||
authUtilsMock.when(() -> AuthorizationUtils.canView(any(), eq(domainUrn))).thenReturn(true);
|
||||
|
||||
// Execute mapping
|
||||
Domain result = DomainMapper.map(mockQueryContext, entityResponse);
|
||||
|
||||
// Should handle empty properties
|
||||
assertNotNull(result);
|
||||
assertNotNull(result.getProperties());
|
||||
assertEquals(
|
||||
result.getProperties().getName(), "Test Domain"); // We set a name because it's required
|
||||
assertNull(result.getProperties().getDescription());
|
||||
assertNull(result.getProperties().getCreatedOn());
|
||||
}
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
private EntityResponse createBasicEntityResponse() {
|
||||
EntityResponse entityResponse = new EntityResponse();
|
||||
entityResponse.setUrn(domainUrn);
|
||||
|
||||
// Create domain key aspect
|
||||
DomainKey domainKey = new DomainKey();
|
||||
domainKey.setId(TEST_DOMAIN_ID);
|
||||
|
||||
EnvelopedAspect domainKeyAspect = new EnvelopedAspect();
|
||||
domainKeyAspect.setValue(new Aspect(domainKey.data()));
|
||||
|
||||
Map<String, EnvelopedAspect> aspects = new HashMap<>();
|
||||
aspects.put(DOMAIN_KEY_ASPECT_NAME, domainKeyAspect);
|
||||
|
||||
entityResponse.setAspects(new EnvelopedAspectMap(aspects));
|
||||
return entityResponse;
|
||||
}
|
||||
|
||||
private void addAspectToResponse(
|
||||
EntityResponse entityResponse, String aspectName, Object aspectData) {
|
||||
EnvelopedAspect aspect = new EnvelopedAspect();
|
||||
aspect.setValue(new Aspect(((com.linkedin.data.template.RecordTemplate) aspectData).data()));
|
||||
entityResponse.getAspects().put(aspectName, aspect);
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package com.linkedin.datahub.graphql.util;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
|
||||
import com.linkedin.common.AuditStamp;
|
||||
import com.linkedin.common.urn.Urn;
|
||||
import com.linkedin.datahub.graphql.generated.ResolvedAuditStamp;
|
||||
import com.linkedin.entity.EntityResponse;
|
||||
import com.linkedin.entity.EnvelopedAspect;
|
||||
import com.linkedin.entity.EnvelopedAspectMap;
|
||||
import java.net.URISyntaxException;
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
public class EntityResponseUtilsTest {
|
||||
@Test
|
||||
public void testExtractAspectCreatedAuditStamp() throws URISyntaxException {
|
||||
// Create a mock EntityResponse
|
||||
EntityResponse entityResponse = new EntityResponse();
|
||||
entityResponse.setUrn(Urn.createFromString("urn:li:corpuser:test"));
|
||||
EnvelopedAspectMap aspectMap = new EnvelopedAspectMap();
|
||||
EnvelopedAspect aspect = new EnvelopedAspect();
|
||||
AuditStamp created = new AuditStamp();
|
||||
created.setActor(Urn.createFromString("urn:li:corpuser:test"));
|
||||
created.setTime(1234567890L);
|
||||
aspect.setCreated(created);
|
||||
aspectMap.put("testAspect", aspect);
|
||||
entityResponse.setAspects(aspectMap);
|
||||
|
||||
// Call the method to be tested
|
||||
ResolvedAuditStamp auditStamp =
|
||||
EntityResponseUtils.extractAspectCreatedAuditStamp(entityResponse, "testAspect");
|
||||
|
||||
// Assert the results
|
||||
assertEquals(auditStamp.getActor().getUrn(), "urn:li:corpuser:test");
|
||||
assertEquals(auditStamp.getTime(), (Long) 1234567890L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractAspectCreatedAuditStampDefault() throws URISyntaxException {
|
||||
// Test with a null EntityResponse
|
||||
ResolvedAuditStamp auditStamp1 =
|
||||
EntityResponseUtils.extractAspectCreatedAuditStamp(null, "testAspect");
|
||||
assertNull(auditStamp1);
|
||||
|
||||
// Test with an EntityResponse with no aspects
|
||||
EntityResponse entityResponse = new EntityResponse();
|
||||
entityResponse.setUrn(Urn.createFromString("urn:li:corpuser:test"));
|
||||
ResolvedAuditStamp auditStamp2 =
|
||||
EntityResponseUtils.extractAspectCreatedAuditStamp(entityResponse, "testAspect");
|
||||
assertNull(auditStamp2);
|
||||
|
||||
// Test with an EntityResponse with an empty aspect map
|
||||
entityResponse.setAspects(new EnvelopedAspectMap());
|
||||
ResolvedAuditStamp auditStamp3 =
|
||||
EntityResponseUtils.extractAspectCreatedAuditStamp(entityResponse, "testAspect");
|
||||
assertNull(auditStamp3);
|
||||
|
||||
// Test with an EntityResponse with a missing aspect
|
||||
EnvelopedAspectMap aspectMap = new EnvelopedAspectMap();
|
||||
aspectMap.put("otherAspect", new EnvelopedAspect());
|
||||
entityResponse.setAspects(aspectMap);
|
||||
ResolvedAuditStamp auditStamp4 =
|
||||
EntityResponseUtils.extractAspectCreatedAuditStamp(entityResponse, "testAspect");
|
||||
assertNull(auditStamp4);
|
||||
|
||||
// Test with an EntityResponse with an aspect with no created field
|
||||
aspectMap.put("testAspect", new EnvelopedAspect());
|
||||
entityResponse.setAspects(aspectMap);
|
||||
ResolvedAuditStamp auditStamp5 =
|
||||
EntityResponseUtils.extractAspectCreatedAuditStamp(entityResponse, "testAspect");
|
||||
assertNull(auditStamp5);
|
||||
}
|
||||
}
|
@ -43,6 +43,7 @@ import {
|
||||
ParentDomainsResult,
|
||||
ParentNodesResult,
|
||||
RawAspect,
|
||||
ResolvedAuditStamp,
|
||||
SchemaMetadata,
|
||||
ScrollResults,
|
||||
SiblingProperties,
|
||||
@ -88,6 +89,7 @@ export type GenericEntityProperties = {
|
||||
sourceRef?: Maybe<string>;
|
||||
businessAttributeDataType?: Maybe<string>;
|
||||
externalUrl?: Maybe<string>;
|
||||
createdOn?: Maybe<ResolvedAuditStamp>;
|
||||
}>;
|
||||
globalTags?: Maybe<GlobalTags>;
|
||||
glossaryTerms?: Maybe<GlossaryTerms>;
|
||||
|
@ -1,9 +1,26 @@
|
||||
import { Text } from '@components';
|
||||
import React from 'react';
|
||||
|
||||
import { useEntityContext } from '@app/entity/shared/EntityContext';
|
||||
import BaseProperty from '@app/entityV2/summary/properties/property/properties/BaseProperty';
|
||||
import { PropertyComponentProps } from '@app/entityV2/summary/properties/types';
|
||||
import { formatTimestamp } from '@app/sharedV2/time/utils';
|
||||
|
||||
export default function CreatedProperty(props: PropertyComponentProps) {
|
||||
// TODO: implement
|
||||
return <BaseProperty {...props} values={[]} renderValue={() => null} />;
|
||||
const { entityData, loading } = useEntityContext();
|
||||
|
||||
const createdTimestamp = entityData?.properties?.createdOn?.time;
|
||||
|
||||
const renderCreated = (timestamp: number) => {
|
||||
return <Text color="gray">{formatTimestamp(timestamp, 'll')}</Text>;
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseProperty
|
||||
{...props}
|
||||
values={createdTimestamp ? [createdTimestamp] : []}
|
||||
renderValue={renderCreated}
|
||||
loading={loading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
8
datahub-web-react/src/app/sharedV2/time/utils.ts
Normal file
8
datahub-web-react/src/app/sharedV2/time/utils.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import dayjs from 'dayjs';
|
||||
import LocalizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
|
||||
dayjs.extend(LocalizedFormat);
|
||||
|
||||
export function formatTimestamp(timestamp: number, format: string) {
|
||||
return dayjs(timestamp).format(format);
|
||||
}
|
@ -62,6 +62,9 @@ fragment dataProductSearchFields on DataProduct {
|
||||
name
|
||||
description
|
||||
externalUrl
|
||||
createdOn {
|
||||
time
|
||||
}
|
||||
}
|
||||
ownership {
|
||||
...ownershipFields
|
||||
|
@ -6,6 +6,9 @@ query getDomain($urn: String!) {
|
||||
properties {
|
||||
name
|
||||
description
|
||||
createdOn {
|
||||
time
|
||||
}
|
||||
}
|
||||
parentDomains {
|
||||
...parentDomainsFields
|
||||
|
@ -110,6 +110,9 @@ fragment glossaryNode on GlossaryNode {
|
||||
properties {
|
||||
name
|
||||
description
|
||||
createdOn {
|
||||
time
|
||||
}
|
||||
}
|
||||
displayProperties {
|
||||
...displayPropertiesFields
|
||||
|
@ -19,6 +19,9 @@ fragment glossaryNodeFields on GlossaryNode {
|
||||
customProperties {
|
||||
...customPropertiesFields
|
||||
}
|
||||
createdOn {
|
||||
time
|
||||
}
|
||||
}
|
||||
ownership {
|
||||
...ownershipFields
|
||||
|
@ -76,6 +76,9 @@ query getGlossaryTerm($urn: String!, $start: Int, $count: Int) {
|
||||
customProperties {
|
||||
...customPropertiesFields
|
||||
}
|
||||
createdOn {
|
||||
time
|
||||
}
|
||||
}
|
||||
schemaMetadata(version: 0) {
|
||||
...schemaMetadataFields
|
||||
|
@ -862,6 +862,9 @@ fragment searchResultsWithoutSchemaField on Entity {
|
||||
key
|
||||
value
|
||||
}
|
||||
createdOn {
|
||||
time
|
||||
}
|
||||
}
|
||||
deprecation {
|
||||
...deprecationFields
|
||||
|
@ -377,6 +377,7 @@ public class Constants {
|
||||
public static final String QUERY_SUBJECTS_ASPECT_NAME = "querySubjects";
|
||||
|
||||
// DataProduct
|
||||
public static final String DATA_PRODUCT_KEY_ASPECT_NAME = "dataProductKey";
|
||||
public static final String DATA_PRODUCT_PROPERTIES_ASPECT_NAME = "dataProductProperties";
|
||||
public static final String DATA_PRODUCTS_ASPECT_NAME = "dataProducts";
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user