Skip to content

Commit

Permalink
Merge branch 'datahub-project:master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
hsheth2 authored Apr 16, 2024
2 parents 693a0ad + 90c1249 commit 9415349
Show file tree
Hide file tree
Showing 180 changed files with 9,807 additions and 311 deletions.
2 changes: 1 addition & 1 deletion buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ dependencies {

compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public class OpenApiEntities {
.add("dataProductProperties")
.add("institutionalMemory")
.add("forms").add("formInfo").add("dynamicFormAssignment")
.add("businessAttributeInfo")
.build();

private final static ImmutableSet<String> ENTITY_EXCLUSIONS = ImmutableSet.<String>builder()
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.linkedin.metadata.graph.SiblingGraphService;
import com.linkedin.metadata.models.registry.EntityRegistry;
import com.linkedin.metadata.recommendation.RecommendationsService;
import com.linkedin.metadata.service.BusinessAttributeService;
import com.linkedin.metadata.service.DataProductService;
import com.linkedin.metadata.service.ERModelRelationshipService;
import com.linkedin.metadata.service.FormService;
Expand Down Expand Up @@ -81,6 +82,7 @@ public class GmsGraphQLEngineArgs {
RestrictedService restrictedService;
int graphQLQueryComplexityLimit;
int graphQLQueryDepthLimit;
BusinessAttributeService businessAttributeService;

// any fork specific args should go below this line
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@ public class FeatureFlags {
private boolean showAccessManagement = false;
private boolean nestedDomainsEnabled = false;
private boolean schemaFieldEntityFetchEnabled = false;
private boolean businessAttributeEntityEnabled = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.linkedin.datahub.graphql.generated.AuthenticatedUser;
import com.linkedin.datahub.graphql.generated.CorpUser;
import com.linkedin.datahub.graphql.generated.PlatformPrivileges;
import com.linkedin.datahub.graphql.resolvers.businessattribute.BusinessAttributeAuthorizationUtils;
import com.linkedin.datahub.graphql.types.corpuser.mappers.CorpUserMapper;
import com.linkedin.entity.EntityResponse;
import com.linkedin.entity.client.EntityClient;
Expand Down Expand Up @@ -86,7 +87,10 @@ public CompletableFuture<AuthenticatedUser> get(DataFetchingEnvironment environm
AuthorizationUtils.canManageOwnershipTypes(context));
platformPrivileges.setManageGlobalAnnouncements(
AuthorizationUtils.canManageGlobalAnnouncements(context));

platformPrivileges.setCreateBusinessAttributes(
BusinessAttributeAuthorizationUtils.canCreateBusinessAttribute(context));
platformPrivileges.setManageBusinessAttributes(
BusinessAttributeAuthorizationUtils.canManageBusinessAttribute(context));
// Construct and return authenticated user object.
final AuthenticatedUser authUser = new AuthenticatedUser();
authUser.setCorpUser(corpUser);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.linkedin.datahub.graphql.resolvers.businessattribute;

import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;
import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.buildMetadataChangeProposalWithUrn;
import static com.linkedin.metadata.Constants.BUSINESS_ATTRIBUTE_ASPECT;

import com.linkedin.businessattribute.BusinessAttributeAssociation;
import com.linkedin.businessattribute.BusinessAttributes;
import com.linkedin.common.urn.BusinessAttributeUrn;
import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.generated.AddBusinessAttributeInput;
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
import com.linkedin.metadata.entity.EntityService;
import com.linkedin.metadata.entity.EntityUtils;
import com.linkedin.mxe.MetadataChangeProposal;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
public class AddBusinessAttributeResolver implements DataFetcher<CompletableFuture<Boolean>> {
private final EntityService entityService;

@Override
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
final QueryContext context = environment.getContext();
final AddBusinessAttributeInput input =
bindArgument(environment.getArgument("input"), AddBusinessAttributeInput.class);
final Urn businessAttributeUrn = UrnUtils.getUrn(input.getBusinessAttributeUrn());
final List<ResourceRefInput> resourceRefInputs = input.getResourceUrn();
validateBusinessAttribute(businessAttributeUrn);
return CompletableFuture.supplyAsync(
() -> {
try {
addBusinessAttributeToResource(
businessAttributeUrn,
resourceRefInputs,
UrnUtils.getUrn(context.getActorUrn()),
entityService);
return true;
} catch (Exception e) {
log.error(
String.format(
"Failed to add Business Attribute %s to resources %s",
businessAttributeUrn, resourceRefInputs));
throw new RuntimeException(
String.format(
"Failed to add Business Attribute %s to resources %s",
businessAttributeUrn, resourceRefInputs),
e);
}
});
}

private void validateBusinessAttribute(Urn businessAttributeUrn) {
if (!entityService.exists(businessAttributeUrn, true)) {
throw new IllegalArgumentException(
String.format("This urn does not exist: %s", businessAttributeUrn));
}
}

private void addBusinessAttributeToResource(
Urn businessAttributeUrn,
List<ResourceRefInput> resourceRefInputs,
Urn actorUrn,
EntityService entityService)
throws URISyntaxException {
List<MetadataChangeProposal> proposals = new ArrayList<>();
for (ResourceRefInput resourceRefInput : resourceRefInputs) {
proposals.add(
buildAddBusinessAttributeToEntityProposal(
businessAttributeUrn, resourceRefInput, entityService, actorUrn));
}
EntityUtils.ingestChangeProposals(proposals, entityService, actorUrn, false);
}

private MetadataChangeProposal buildAddBusinessAttributeToEntityProposal(
Urn businessAttributeUrn,
ResourceRefInput resource,
EntityService entityService,
Urn actorUrn)
throws URISyntaxException {
BusinessAttributes businessAttributes =
(BusinessAttributes)
EntityUtils.getAspectFromEntity(
resource.getResourceUrn(),
BUSINESS_ATTRIBUTE_ASPECT,
entityService,
new BusinessAttributes());
if (!businessAttributes.hasBusinessAttribute()) {
businessAttributes.setBusinessAttribute(new BusinessAttributeAssociation());
}
BusinessAttributeAssociation businessAttributeAssociation =
businessAttributes.getBusinessAttribute();
businessAttributeAssociation.setBusinessAttributeUrn(
BusinessAttributeUrn.createFromUrn(businessAttributeUrn));
businessAttributes.setBusinessAttribute(businessAttributeAssociation);
return buildMetadataChangeProposalWithUrn(
UrnUtils.getUrn(resource.getResourceUrn()), BUSINESS_ATTRIBUTE_ASPECT, businessAttributes);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.linkedin.datahub.graphql.resolvers.businessattribute;

import com.datahub.authorization.AuthUtil;
import com.datahub.authorization.ConjunctivePrivilegeGroup;
import com.datahub.authorization.DisjunctivePrivilegeGroup;
import com.google.common.collect.ImmutableList;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.metadata.authorization.PoliciesConfig;
import javax.annotation.Nonnull;

public class BusinessAttributeAuthorizationUtils {
private BusinessAttributeAuthorizationUtils() {}

public static boolean canCreateBusinessAttribute(@Nonnull QueryContext context) {
final DisjunctivePrivilegeGroup orPrivilegeGroups =
new DisjunctivePrivilegeGroup(
ImmutableList.of(
new ConjunctivePrivilegeGroup(
ImmutableList.of(PoliciesConfig.CREATE_BUSINESS_ATTRIBUTE_PRIVILEGE.getType())),
new ConjunctivePrivilegeGroup(
ImmutableList.of(
PoliciesConfig.MANAGE_BUSINESS_ATTRIBUTE_PRIVILEGE.getType()))));
return AuthUtil.isAuthorized(
context.getAuthorizer(), context.getActorUrn(), orPrivilegeGroups, null);
}

public static boolean canManageBusinessAttribute(@Nonnull QueryContext context) {
final DisjunctivePrivilegeGroup orPrivilegeGroups =
new DisjunctivePrivilegeGroup(
ImmutableList.of(
new ConjunctivePrivilegeGroup(
ImmutableList.of(
PoliciesConfig.MANAGE_BUSINESS_ATTRIBUTE_PRIVILEGE.getType()))));
return AuthUtil.isAuthorized(
context.getAuthorizer(), context.getActorUrn(), orPrivilegeGroups, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package com.linkedin.datahub.graphql.resolvers.businessattribute;

import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;
import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.buildMetadataChangeProposalWithKey;
import static com.linkedin.metadata.Constants.BUSINESS_ATTRIBUTE_ENTITY_NAME;
import static com.linkedin.metadata.Constants.BUSINESS_ATTRIBUTE_INFO_ASPECT_NAME;

import com.linkedin.businessattribute.BusinessAttributeInfo;
import com.linkedin.businessattribute.BusinessAttributeKey;
import com.linkedin.common.AuditStamp;
import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.data.template.SetMode;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.datahub.graphql.exception.DataHubGraphQLErrorCode;
import com.linkedin.datahub.graphql.exception.DataHubGraphQLException;
import com.linkedin.datahub.graphql.generated.BusinessAttribute;
import com.linkedin.datahub.graphql.generated.CreateBusinessAttributeInput;
import com.linkedin.datahub.graphql.generated.OwnerEntityType;
import com.linkedin.datahub.graphql.resolvers.mutate.util.BusinessAttributeUtils;
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
import com.linkedin.datahub.graphql.types.businessattribute.mappers.BusinessAttributeMapper;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.metadata.entity.EntityService;
import com.linkedin.metadata.service.BusinessAttributeService;
import com.linkedin.metadata.utils.EntityKeyUtils;
import com.linkedin.mxe.MetadataChangeProposal;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RequiredArgsConstructor
public class CreateBusinessAttributeResolver
implements DataFetcher<CompletableFuture<BusinessAttribute>> {
private final EntityClient _entityClient;
private final EntityService _entityService;
private final BusinessAttributeService businessAttributeService;

@Override
public CompletableFuture<BusinessAttribute> get(DataFetchingEnvironment environment)
throws Exception {
final QueryContext context = environment.getContext();
CreateBusinessAttributeInput input =
bindArgument(environment.getArgument("input"), CreateBusinessAttributeInput.class);
if (!BusinessAttributeAuthorizationUtils.canCreateBusinessAttribute(context)) {
throw new AuthorizationException(
"Unauthorized to perform this action. Please contact your DataHub administrator.");
}
return CompletableFuture.supplyAsync(
() -> {
try {
final BusinessAttributeKey businessAttributeKey = new BusinessAttributeKey();
String id = input.getId() != null ? input.getId() : UUID.randomUUID().toString();
businessAttributeKey.setId(id);

if (_entityClient.exists(
EntityKeyUtils.convertEntityKeyToUrn(
businessAttributeKey, BUSINESS_ATTRIBUTE_ENTITY_NAME),
context.getAuthentication())) {
throw new IllegalArgumentException("This Business Attribute already exists!");
}

if (BusinessAttributeUtils.hasNameConflict(input.getName(), context, _entityClient)) {
throw new DataHubGraphQLException(
String.format(
"\"%s\" already exists as Business Attribute. Please pick a unique name.",
input.getName()),
DataHubGraphQLErrorCode.CONFLICT);
}

// Create the MCP
final MetadataChangeProposal changeProposal =
buildMetadataChangeProposalWithKey(
businessAttributeKey,
BUSINESS_ATTRIBUTE_ENTITY_NAME,
BUSINESS_ATTRIBUTE_INFO_ASPECT_NAME,
mapBusinessAttributeInfo(input, context));

// Ingest the MCP
Urn businessAttributeUrn =
UrnUtils.getUrn(
_entityClient.ingestProposal(changeProposal, context.getAuthentication()));
OwnerUtils.addCreatorAsOwner(
context,
businessAttributeUrn.toString(),
OwnerEntityType.CORP_USER,
_entityService);
return BusinessAttributeMapper.map(
context,
businessAttributeService.getBusinessAttributeEntityResponse(
businessAttributeUrn, context.getAuthentication()));

} catch (DataHubGraphQLException e) {
throw e;
} catch (Exception e) {
log.error(
"Failed to create Business Attribute with name: {}: {}",
input.getName(),
e.getMessage());
throw new RuntimeException(
String.format("Failed to create Business Attribute with name: %s", input.getName()),
e);
}
});
}

private BusinessAttributeInfo mapBusinessAttributeInfo(
CreateBusinessAttributeInput input, QueryContext context) {
final BusinessAttributeInfo info = new BusinessAttributeInfo();
info.setFieldPath(input.getName(), SetMode.DISALLOW_NULL);
info.setName(input.getName(), SetMode.DISALLOW_NULL);
info.setDescription(input.getDescription(), SetMode.IGNORE_NULL);
info.setType(
BusinessAttributeUtils.mapSchemaFieldDataType(input.getType()), SetMode.IGNORE_NULL);
info.setCreated(
new AuditStamp()
.setActor(UrnUtils.getUrn(context.getActorUrn()))
.setTime(System.currentTimeMillis()));
info.setLastModified(
new AuditStamp()
.setActor(UrnUtils.getUrn(context.getActorUrn()))
.setTime(System.currentTimeMillis()));
return info;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.linkedin.datahub.graphql.resolvers.businessattribute;

import com.linkedin.common.urn.Urn;
import com.linkedin.common.urn.UrnUtils;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.exception.AuthorizationException;
import com.linkedin.entity.client.EntityClient;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import java.util.concurrent.CompletableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

/** Resolver responsible for hard deleting a particular Business Attribute */
@Slf4j
@RequiredArgsConstructor
public class DeleteBusinessAttributeResolver implements DataFetcher<CompletableFuture<Boolean>> {
private final EntityClient _entityClient;

@Override
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
final QueryContext context = environment.getContext();
final Urn businessAttributeUrn = UrnUtils.getUrn(environment.getArgument("urn"));
if (!BusinessAttributeAuthorizationUtils.canManageBusinessAttribute(context)) {
throw new AuthorizationException(
"Unauthorized to perform this action. Please contact your DataHub administrator.");
}
if (!_entityClient.exists(businessAttributeUrn, context.getAuthentication())) {
throw new RuntimeException(
String.format("This urn does not exist: %s", businessAttributeUrn));
}
return CompletableFuture.supplyAsync(
() -> {
try {
_entityClient.deleteEntity(businessAttributeUrn, context.getAuthentication());
CompletableFuture.runAsync(
() -> {
try {
_entityClient.deleteEntityReferences(
businessAttributeUrn, context.getAuthentication());
} catch (Exception e) {
log.error(
String.format(
"Exception while attempting to clear all entity references for Business Attribute with urn %s",
businessAttributeUrn),
e);
}
});
return true;
} catch (Exception e) {
throw new RuntimeException(
String.format(
"Failed to delete Business Attribute with urn %s", businessAttributeUrn),
e);
}
});
}
}
Loading

0 comments on commit 9415349

Please sign in to comment.