Skip to content

Commit

Permalink
feat(structuredProperties) Add CRUD graphql APIs for structured prope…
Browse files Browse the repository at this point in the history
…rty entities (datahub-project#10826)
  • Loading branch information
chriscollins3456 authored and aviv-julienjehannet committed Jul 17, 2024
1 parent 817000d commit 519819d
Show file tree
Hide file tree
Showing 11 changed files with 948 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,9 @@
import com.linkedin.datahub.graphql.resolvers.settings.view.UpdateGlobalViewsSettingsResolver;
import com.linkedin.datahub.graphql.resolvers.step.BatchGetStepStatesResolver;
import com.linkedin.datahub.graphql.resolvers.step.BatchUpdateStepStatesResolver;
import com.linkedin.datahub.graphql.resolvers.structuredproperties.CreateStructuredPropertyResolver;
import com.linkedin.datahub.graphql.resolvers.structuredproperties.RemoveStructuredPropertiesResolver;
import com.linkedin.datahub.graphql.resolvers.structuredproperties.UpdateStructuredPropertyResolver;
import com.linkedin.datahub.graphql.resolvers.structuredproperties.UpsertStructuredPropertiesResolver;
import com.linkedin.datahub.graphql.resolvers.tag.CreateTagResolver;
import com.linkedin.datahub.graphql.resolvers.tag.DeleteTagResolver;
Expand Down Expand Up @@ -1321,6 +1324,15 @@ private void configureMutationResolvers(final RuntimeWiring.Builder builder) {
.dataFetcher(
"upsertStructuredProperties",
new UpsertStructuredPropertiesResolver(this.entityClient))
.dataFetcher(
"removeStructuredProperties",
new RemoveStructuredPropertiesResolver(this.entityClient))
.dataFetcher(
"createStructuredProperty",
new CreateStructuredPropertyResolver(this.entityClient))
.dataFetcher(
"updateStructuredProperty",
new UpdateStructuredPropertyResolver(this.entityClient))
.dataFetcher("raiseIncident", new RaiseIncidentResolver(this.entityClient))
.dataFetcher(
"updateIncidentStatus",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,13 @@ public static <T> T restrictEntity(@Nonnull Object entity, Class<T> clazz) {
}
}

public static boolean canManageStructuredProperties(@Nonnull QueryContext context) {
return AuthUtil.isAuthorized(
context.getAuthorizer(),
context.getActorUrn(),
PoliciesConfig.MANAGE_STRUCTURED_PROPERTIES_PRIVILEGE);
}

public static boolean canManageForms(@Nonnull QueryContext context) {
return AuthUtil.isAuthorized(
context.getAuthorizer(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package com.linkedin.datahub.graphql.resolvers.structuredproperties;

import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;
import static com.linkedin.metadata.Constants.STRUCTURED_PROPERTY_ENTITY_NAME;

import com.linkedin.common.urn.Urn;
import com.linkedin.data.template.SetMode;
import com.linkedin.data.template.StringArray;
import com.linkedin.data.template.StringArrayMap;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.CreateStructuredPropertyInput;
import com.linkedin.datahub.graphql.generated.StructuredPropertyEntity;
import com.linkedin.datahub.graphql.types.structuredproperty.StructuredPropertyMapper;
import com.linkedin.entity.EntityResponse;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.aspect.patch.builder.StructuredPropertyDefinitionPatchBuilder;
import com.linkedin.metadata.utils.EntityKeyUtils;
import com.linkedin.mxe.MetadataChangeProposal;
import com.linkedin.structured.PrimitivePropertyValue;
import com.linkedin.structured.PropertyCardinality;
import com.linkedin.structured.PropertyValue;
import com.linkedin.structured.StructuredPropertyKey;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nonnull;

public class CreateStructuredPropertyResolver
implements DataFetcher<CompletableFuture<StructuredPropertyEntity>> {

private final EntityClient _entityClient;

public CreateStructuredPropertyResolver(@Nonnull final EntityClient entityClient) {
_entityClient = Objects.requireNonNull(entityClient, "entityClient must not be null");
}

@Override
public CompletableFuture<StructuredPropertyEntity> get(final DataFetchingEnvironment environment)
throws Exception {
final QueryContext context = environment.getContext();

final CreateStructuredPropertyInput input =
bindArgument(environment.getArgument("input"), CreateStructuredPropertyInput.class);

return CompletableFuture.supplyAsync(
() -> {
try {
if (!AuthorizationUtils.canManageStructuredProperties(context)) {
throw new AuthorizationException(
"Unable to create structured property. Please contact your admin.");
}
final StructuredPropertyKey key = new StructuredPropertyKey();
final String id = input.getId() != null ? input.getId() : UUID.randomUUID().toString();
key.setId(id);
final Urn propertyUrn =
EntityKeyUtils.convertEntityKeyToUrn(key, STRUCTURED_PROPERTY_ENTITY_NAME);
StructuredPropertyDefinitionPatchBuilder builder =
new StructuredPropertyDefinitionPatchBuilder().urn(propertyUrn);

builder.setQualifiedName(input.getQualifiedName());
builder.setValueType(input.getValueType());
input.getEntityTypes().forEach(builder::addEntityType);
if (input.getDisplayName() != null) {
builder.setDisplayName(input.getDisplayName());
}
if (input.getDescription() != null) {
builder.setDescription(input.getDescription());
}
if (input.getImmutable() != null) {
builder.setImmutable(input.getImmutable());
}
if (input.getTypeQualifier() != null) {
buildTypeQualifier(input, builder);
}
if (input.getAllowedValues() != null) {
buildAllowedValues(input, builder);
}
if (input.getCardinality() != null) {
builder.setCardinality(
PropertyCardinality.valueOf(input.getCardinality().toString()));
}

MetadataChangeProposal mcp = builder.build();
_entityClient.ingestProposal(context.getOperationContext(), mcp, false);

EntityResponse response =
_entityClient.getV2(
context.getOperationContext(),
STRUCTURED_PROPERTY_ENTITY_NAME,
propertyUrn,
null);
return StructuredPropertyMapper.map(context, response);
} catch (Exception e) {
throw new RuntimeException(
String.format("Failed to perform update against input %s", input), e);
}
});
}

private void buildTypeQualifier(
@Nonnull final CreateStructuredPropertyInput input,
@Nonnull final StructuredPropertyDefinitionPatchBuilder builder) {
if (input.getTypeQualifier().getAllowedTypes() != null) {
final StringArrayMap typeQualifier = new StringArrayMap();
StringArray allowedTypes = new StringArray();
allowedTypes.addAll(input.getTypeQualifier().getAllowedTypes());
typeQualifier.put("allowedTypes", allowedTypes);
builder.setTypeQualifier(typeQualifier);
}
}

private void buildAllowedValues(
@Nonnull final CreateStructuredPropertyInput input,
@Nonnull final StructuredPropertyDefinitionPatchBuilder builder) {
input
.getAllowedValues()
.forEach(
allowedValueInput -> {
PropertyValue value = new PropertyValue();
PrimitivePropertyValue primitiveValue = new PrimitivePropertyValue();
if (allowedValueInput.getStringValue() != null) {
primitiveValue.setString(allowedValueInput.getStringValue());
}
if (allowedValueInput.getNumberValue() != null) {
primitiveValue.setDouble(allowedValueInput.getNumberValue().doubleValue());
}
value.setValue(primitiveValue);
value.setDescription(allowedValueInput.getDescription(), SetMode.IGNORE_NULL);
builder.addAllowedValue(value);
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.linkedin.datahub.graphql.resolvers.structuredproperties;

import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;

import com.google.common.collect.ImmutableSet;
import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.generated.RemoveStructuredPropertiesInput;
import com.linkedin.datahub.graphql.types.structuredproperty.StructuredPropertiesMapper;
import com.linkedin.entity.EntityResponse;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.Constants;
import com.linkedin.metadata.aspect.patch.builder.StructuredPropertiesPatchBuilder;
import com.linkedin.mxe.MetadataChangeProposal;
import com.linkedin.structured.StructuredProperties;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nonnull;

public class RemoveStructuredPropertiesResolver
implements DataFetcher<
CompletableFuture<com.linkedin.datahub.graphql.generated.StructuredProperties>> {

private final EntityClient _entityClient;

public RemoveStructuredPropertiesResolver(@Nonnull final EntityClient entityClient) {
_entityClient = Objects.requireNonNull(entityClient, "entityClient must not be null");
}

@Override
public CompletableFuture<com.linkedin.datahub.graphql.generated.StructuredProperties> get(
final DataFetchingEnvironment environment) throws Exception {
final QueryContext context = environment.getContext();

final RemoveStructuredPropertiesInput input =
bindArgument(environment.getArgument("input"), RemoveStructuredPropertiesInput.class);
final Urn assetUrn = UrnUtils.getUrn(input.getAssetUrn());

return CompletableFuture.supplyAsync(
() -> {
try {
// check authorization first
if (!AuthorizationUtils.canEditProperties(assetUrn, context)) {
throw new AuthorizationException(
String.format(
"Not authorized to update properties on the gives urn %s", assetUrn));
}

if (!_entityClient.exists(context.getOperationContext(), assetUrn)) {
throw new RuntimeException(
String.format("Asset with provided urn %s does not exist", assetUrn));
}

StructuredPropertiesPatchBuilder patchBuilder =
new StructuredPropertiesPatchBuilder().urn(assetUrn);

input
.getStructuredPropertyUrns()
.forEach(
propertyUrn -> {
patchBuilder.removeProperty(UrnUtils.getUrn(propertyUrn));
});

// ingest change proposal
final MetadataChangeProposal structuredPropertiesProposal = patchBuilder.build();

_entityClient.ingestProposal(
context.getOperationContext(), structuredPropertiesProposal, false);

EntityResponse response =
_entityClient.getV2(
context.getOperationContext(),
assetUrn.getEntityType(),
assetUrn,
ImmutableSet.of(Constants.STRUCTURED_PROPERTIES_ASPECT_NAME));

if (response == null
|| response.getAspects().get(Constants.STRUCTURED_PROPERTIES_ASPECT_NAME) == null) {
throw new RuntimeException(
String.format("Failed to fetch structured properties from entity %s", assetUrn));
}

StructuredProperties structuredProperties =
new StructuredProperties(
response
.getAspects()
.get(Constants.STRUCTURED_PROPERTIES_ASPECT_NAME)
.getValue()
.data());

return StructuredPropertiesMapper.map(context, structuredProperties);
} catch (Exception e) {
throw new RuntimeException(
String.format("Failed to perform update against input %s", input), e);
}
});
}
}
Loading

0 comments on commit 519819d

Please sign in to comment.