From b57de90fd1f64f82c9937a71d87be6a5b0fb4d5e Mon Sep 17 00:00:00 2001 From: Chris Collins Date: Wed, 18 Sep 2024 15:16:40 -0400 Subject: [PATCH] feat(structuredProps) Add created and lastModified timestamps to structured prop entity (#11419) --- .../datahub/graphql/GmsGraphQLEngine.java | 1 + .../CreateStructuredPropertyResolver.java | 2 ++ .../UpdateStructuredPropertyResolver.java | 1 + .../graphql/types/mappers/MapperUtils.java | 12 ++++++++ .../StructuredPropertyMapper.java | 8 +++++ .../src/main/resources/entity.graphql | 5 ++++ .../src/main/resources/properties.graphql | 10 +++++++ ...ucturedPropertyDefinitionPatchBuilder.java | 29 +++++++++++++++++++ .../StructuredPropertyDefinition.pdl | 23 +++++++++++++++ 9 files changed, 91 insertions(+) diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java index de77ff9444c6e..bb4c26d89f5ce 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java @@ -1034,6 +1034,7 @@ private void configureQueryResolvers(final RuntimeWiring.Builder builder) { .dataFetcher("assertion", getResolver(assertionType)) .dataFetcher("form", getResolver(formType)) .dataFetcher("view", getResolver(dataHubViewType)) + .dataFetcher("structuredProperty", getResolver(structuredPropertyType)) .dataFetcher("listPolicies", new ListPoliciesResolver(this.entityClient)) .dataFetcher("getGrantedPrivileges", new GetGrantedPrivilegesResolver()) .dataFetcher("listUsers", new ListUsersResolver(this.entityClient)) diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/structuredproperties/CreateStructuredPropertyResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/structuredproperties/CreateStructuredPropertyResolver.java index 3be7ea505abbf..328f63b893d06 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/structuredproperties/CreateStructuredPropertyResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/structuredproperties/CreateStructuredPropertyResolver.java @@ -83,6 +83,8 @@ public CompletableFuture get(final DataFetchingEnviron builder.setCardinality( PropertyCardinality.valueOf(input.getCardinality().toString())); } + builder.setCreated(context.getOperationContext().getAuditStamp()); + builder.setLastModified(context.getOperationContext().getAuditStamp()); MetadataChangeProposal mcp = builder.build(); _entityClient.ingestProposal(context.getOperationContext(), mcp, false); diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/structuredproperties/UpdateStructuredPropertyResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/structuredproperties/UpdateStructuredPropertyResolver.java index 2549f303bacd9..c432281ec1684 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/structuredproperties/UpdateStructuredPropertyResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/structuredproperties/UpdateStructuredPropertyResolver.java @@ -76,6 +76,7 @@ public CompletableFuture get(final DataFetchingEnviron if (input.getNewEntityTypes() != null) { input.getNewEntityTypes().forEach(builder::addEntityType); } + builder.setLastModified(context.getOperationContext().getAuditStamp()); MetadataChangeProposal mcp = builder.build(); _entityClient.ingestProposal(context.getOperationContext(), mcp, false); diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/mappers/MapperUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/mappers/MapperUtils.java index 0eb74210971d9..0d69e62c621a6 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/mappers/MapperUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/mappers/MapperUtils.java @@ -3,15 +3,18 @@ import static com.linkedin.datahub.graphql.util.SearchInsightsUtil.*; import static com.linkedin.metadata.utils.SearchUtil.*; +import com.linkedin.common.AuditStamp; import com.linkedin.common.UrnArray; import com.linkedin.common.urn.Urn; import com.linkedin.data.template.StringMap; import com.linkedin.datahub.graphql.QueryContext; import com.linkedin.datahub.graphql.generated.AggregationMetadata; +import com.linkedin.datahub.graphql.generated.CorpUser; import com.linkedin.datahub.graphql.generated.EntityPath; import com.linkedin.datahub.graphql.generated.ExtraProperty; import com.linkedin.datahub.graphql.generated.FacetMetadata; import com.linkedin.datahub.graphql.generated.MatchedField; +import com.linkedin.datahub.graphql.generated.ResolvedAuditStamp; import com.linkedin.datahub.graphql.generated.SearchResult; import com.linkedin.datahub.graphql.generated.SearchSuggestion; import com.linkedin.datahub.graphql.types.common.mappers.UrnToEntityMapper; @@ -132,4 +135,13 @@ public static EntityPath mapPath(@Nullable final QueryContext context, UrnArray path.stream().map(p -> UrnToEntityMapper.map(context, p)).collect(Collectors.toList())); return entityPath; } + + public static ResolvedAuditStamp createResolvedAuditStamp(AuditStamp auditStamp) { + final ResolvedAuditStamp resolvedAuditStamp = new ResolvedAuditStamp(); + final CorpUser emptyCreatedUser = new CorpUser(); + emptyCreatedUser.setUrn(auditStamp.getActor().toString()); + resolvedAuditStamp.setActor(emptyCreatedUser); + resolvedAuditStamp.setTime(auditStamp.getTime()); + return resolvedAuditStamp; + } } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/structuredproperty/StructuredPropertyMapper.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/structuredproperty/StructuredPropertyMapper.java index ff54131506a7c..cacb6958dc202 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/structuredproperty/StructuredPropertyMapper.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/types/structuredproperty/StructuredPropertyMapper.java @@ -17,6 +17,7 @@ import com.linkedin.datahub.graphql.generated.StructuredPropertyEntity; import com.linkedin.datahub.graphql.generated.TypeQualifier; import com.linkedin.datahub.graphql.types.common.mappers.util.MappingHelper; +import com.linkedin.datahub.graphql.types.mappers.MapperUtils; import com.linkedin.datahub.graphql.types.mappers.ModelMapper; import com.linkedin.entity.EntityResponse; import com.linkedin.entity.EnvelopedAspectMap; @@ -74,6 +75,13 @@ private void mapStructuredPropertyDefinition( if (gmsDefinition.hasTypeQualifier()) { definition.setTypeQualifier(mapTypeQualifier(gmsDefinition.getTypeQualifier())); } + if (gmsDefinition.getCreated() != null) { + definition.setCreated(MapperUtils.createResolvedAuditStamp(gmsDefinition.getCreated())); + } + if (gmsDefinition.getLastModified() != null) { + definition.setLastModified( + MapperUtils.createResolvedAuditStamp(gmsDefinition.getLastModified())); + } definition.setEntityTypes( gmsDefinition.getEntityTypes().stream() .map(this::createEntityTypeEntity) diff --git a/datahub-graphql-core/src/main/resources/entity.graphql b/datahub-graphql-core/src/main/resources/entity.graphql index 52e81f8094dea..a2e2fe9163f53 100644 --- a/datahub-graphql-core/src/main/resources/entity.graphql +++ b/datahub-graphql-core/src/main/resources/entity.graphql @@ -84,6 +84,11 @@ type Query { """ role(urn: String!): Role + """ + Fetch a Structured Property by primary key (urn) + """ + structuredProperty(urn: String!): StructuredPropertyEntity + """ Fetch a ERModelRelationship by primary key (urn) """ diff --git a/datahub-graphql-core/src/main/resources/properties.graphql b/datahub-graphql-core/src/main/resources/properties.graphql index dfe8468645681..6c1f910e02b0e 100644 --- a/datahub-graphql-core/src/main/resources/properties.graphql +++ b/datahub-graphql-core/src/main/resources/properties.graphql @@ -95,6 +95,16 @@ type StructuredPropertyDefinition { Whether or not this structured property is immutable """ immutable: Boolean! + + """ + Audit stamp for when this structured property was created + """ + created: ResolvedAuditStamp + + """ + Audit stamp for when this structured property was last modified + """ + lastModified: ResolvedAuditStamp } """ diff --git a/entity-registry/src/main/java/com/linkedin/metadata/aspect/patch/builder/StructuredPropertyDefinitionPatchBuilder.java b/entity-registry/src/main/java/com/linkedin/metadata/aspect/patch/builder/StructuredPropertyDefinitionPatchBuilder.java index 0811e2f52d003..1feae36b1462d 100644 --- a/entity-registry/src/main/java/com/linkedin/metadata/aspect/patch/builder/StructuredPropertyDefinitionPatchBuilder.java +++ b/entity-registry/src/main/java/com/linkedin/metadata/aspect/patch/builder/StructuredPropertyDefinitionPatchBuilder.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.linkedin.common.AuditStamp; import com.linkedin.data.template.StringArrayMap; import com.linkedin.metadata.aspect.patch.PatchOperationType; import com.linkedin.structured.PropertyCardinality; @@ -29,6 +30,10 @@ public class StructuredPropertyDefinitionPatchBuilder public static final String ENTITY_TYPES_FIELD = "entityTypes"; public static final String DESCRIPTION_FIELD = "description"; public static final String IMMUTABLE_FIELD = "immutable"; + private static final String LAST_MODIFIED_KEY = "lastModified"; + private static final String CREATED_KEY = "created"; + private static final String TIME_KEY = "time"; + private static final String ACTOR_KEY = "actor"; // can only be used when creating a new structured property public StructuredPropertyDefinitionPatchBuilder setQualifiedName(@Nonnull String name) { @@ -134,6 +139,30 @@ public StructuredPropertyDefinitionPatchBuilder setImmutable(boolean immutable) return this; } + public StructuredPropertyDefinitionPatchBuilder setLastModified( + @Nonnull AuditStamp lastModified) { + ObjectNode lastModifiedValue = instance.objectNode(); + lastModifiedValue.put(TIME_KEY, lastModified.getTime()); + lastModifiedValue.put(ACTOR_KEY, lastModified.getActor().toString()); + + pathValues.add( + ImmutableTriple.of( + PatchOperationType.ADD.getValue(), "/" + LAST_MODIFIED_KEY, lastModifiedValue)); + + return this; + } + + public StructuredPropertyDefinitionPatchBuilder setCreated(@Nonnull AuditStamp created) { + ObjectNode createdValue = instance.objectNode(); + createdValue.put(TIME_KEY, created.getTime()); + createdValue.put(ACTOR_KEY, created.getActor().toString()); + + pathValues.add( + ImmutableTriple.of(PatchOperationType.ADD.getValue(), "/" + CREATED_KEY, createdValue)); + + return this; + } + @Override protected String getAspectName() { return STRUCTURED_PROPERTY_DEFINITION_ASPECT_NAME; diff --git a/metadata-models/src/main/pegasus/com/linkedin/structured/StructuredPropertyDefinition.pdl b/metadata-models/src/main/pegasus/com/linkedin/structured/StructuredPropertyDefinition.pdl index bf0bf65099b2e..3ddb2d2e571da 100644 --- a/metadata-models/src/main/pegasus/com/linkedin/structured/StructuredPropertyDefinition.pdl +++ b/metadata-models/src/main/pegasus/com/linkedin/structured/StructuredPropertyDefinition.pdl @@ -1,5 +1,6 @@ namespace com.linkedin.structured +import com.linkedin.common.AuditStamp import com.linkedin.common.Urn import com.linkedin.datahub.DataHubSearchConfig @@ -86,5 +87,27 @@ record StructuredPropertyDefinition { * 20240610, 20240611 */ version: optional string + + /** + * Created Audit stamp + */ + @Searchable = { + "/time": { + "fieldName": "createdTime", + "fieldType": "DATETIME" + } + } + created: optional AuditStamp + + /** + * Created Audit stamp + */ + @Searchable = { + "/time": { + "fieldName": "lastModified", + "fieldType": "DATETIME" + } + } + lastModified: optional AuditStamp }