Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(structuredProperties) Add CRUD graphql APIs for structured property entities #10826

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,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;
Comment on lines +291 to +293
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add missing import for UpsertStructuredPropertiesResolver.

The import statement for UpsertStructuredPropertiesResolver is missing, which might lead to compilation errors.

+import com.linkedin.datahub.graphql.resolvers.structuredproperties.UpsertStructuredPropertiesResolver;
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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.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.structuredproperties.UpsertStructuredPropertiesResolver;
import com.linkedin.datahub.graphql.resolvers.tag.CreateTagResolver;
import com.linkedin.datahub.graphql.resolvers.tag.DeleteTagResolver;
Expand Down Expand Up @@ -1316,6 +1319,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 isAuthorized(
@Nonnull Authorizer authorizer,
@Nonnull String actor,
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

Issue with Import Statement or Missing File

The import statement com.linkedin.datahub.graphql.generated.RemoveStructuredPropertiesInput in RemoveStructuredPropertiesResolver.java does not match any existing file in the repository. Additionally, the expected directory structure for generated is not found within the com/linkedin/datahub/graphql path.

  • Ensure that the file RemoveStructuredPropertiesInput.java exists and is correctly placed in the com/linkedin/datahub/graphql/generated directory.
  • Verify the import statement for correct casing and path.
Analysis chain

Check for case sensitivity in import statements.

Ensure that the import com.linkedin.datahub.graphql.generated.RemoveStructuredPropertiesInput is correctly cased and matches the file structure.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify the case sensitivity of the import statement.

# Test: Check if the file exists with the exact case.
fd --type f "RemoveStructuredPropertiesInput.java"

Length of output: 50



Script:

#!/bin/bash
# Description: Verify the case sensitivity of the import statement by checking different case variations.

# Test: Check if the file exists with various case variations.
fd --type f -e java | grep -i "RemoveStructuredPropertiesInput"

# Test: Verify the directory structure for the package path.
fd --type d "generated" | grep "com/linkedin/datahub/graphql"

Length of output: 123



Script:

#!/bin/bash
# Description: Perform a broader search for related files and directories.

# Test: Search for any files related to RemoveStructuredPropertiesInput with different case variations.
fd --type f -e java | grep -i "RemoveStructuredProperties"

# Test: Search for any directories related to the generated package path.
fd --type d | grep -i "generated"

Length of output: 1194

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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding null checks for context.

While context is typically provided by the GraphQL environment, adding a null check can prevent potential NullPointerException.

-    final QueryContext context = environment.getContext();
+    final QueryContext context = Objects.requireNonNull(environment.getContext(), "context must not be null");
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
final QueryContext context = environment.getContext();
final QueryContext context = Objects.requireNonNull(environment.getContext(), "context must not be null");


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

return CompletableFuture.supplyAsync(
() -> {
try {
Comment on lines +44 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider handling CompletableFuture exceptions.

Using CompletableFuture.supplyAsync is appropriate, but consider handling exceptions more gracefully within the async block.

-        () -> {
+        () -> { 
+            try {
+                // existing code
+            } catch (Exception e) {
+                throw new CompletionException(e);
+            }
+        }

Committable suggestion was skipped due to low confidence.

// 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
Loading