Skip to content

Commit c35f360

Browse files
deepgarg760ppurswanPrithviVISAaditigup-visaaabharti-visa
authored
feat(ui/backend/openapi/docs) : Add support for Business Attributes (#9863)
Co-authored-by: ppurswan <[email protected]> Co-authored-by: PrithviVISA <[email protected]> Co-authored-by: aditigup <[email protected]> Co-authored-by: Bharti, Aakash <[email protected]> Co-authored-by: Singh, Himanshu <[email protected]> Co-authored-by: Shukla, Amit <[email protected]> Co-authored-by: Kartikey Khandelwal <[email protected]>
1 parent 5d5661b commit c35f360

File tree

167 files changed

+8671
-311
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

167 files changed

+8671
-311
lines changed

buildSrc/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ dependencies {
2525

2626
compileOnly 'org.projectlombok:lombok:1.18.30'
2727
annotationProcessor 'org.projectlombok:lombok:1.18.30'
28-
}
28+
}

buildSrc/src/main/java/io/datahubproject/OpenApiEntities.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public class OpenApiEntities {
6060
.add("dataProductProperties")
6161
.add("institutionalMemory")
6262
.add("forms").add("formInfo").add("dynamicFormAssignment")
63+
.add("businessAttributeInfo")
6364
.build();
6465

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

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngine.java

Lines changed: 259 additions & 184 deletions
Large diffs are not rendered by default.

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/GmsGraphQLEngineArgs.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.linkedin.metadata.graph.SiblingGraphService;
2424
import com.linkedin.metadata.models.registry.EntityRegistry;
2525
import com.linkedin.metadata.recommendation.RecommendationsService;
26+
import com.linkedin.metadata.service.BusinessAttributeService;
2627
import com.linkedin.metadata.service.DataProductService;
2728
import com.linkedin.metadata.service.ERModelRelationshipService;
2829
import com.linkedin.metadata.service.FormService;
@@ -81,6 +82,7 @@ public class GmsGraphQLEngineArgs {
8182
RestrictedService restrictedService;
8283
int graphQLQueryComplexityLimit;
8384
int graphQLQueryDepthLimit;
85+
BusinessAttributeService businessAttributeService;
8486

8587
// any fork specific args should go below this line
8688
}

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/featureflags/FeatureFlags.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ public class FeatureFlags {
1919
private boolean showAccessManagement = false;
2020
private boolean nestedDomainsEnabled = false;
2121
private boolean schemaFieldEntityFetchEnabled = false;
22+
private boolean businessAttributeEntityEnabled = false;
2223
}

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/MeResolver.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import com.linkedin.datahub.graphql.generated.AuthenticatedUser;
1616
import com.linkedin.datahub.graphql.generated.CorpUser;
1717
import com.linkedin.datahub.graphql.generated.PlatformPrivileges;
18+
import com.linkedin.datahub.graphql.resolvers.businessattribute.BusinessAttributeAuthorizationUtils;
1819
import com.linkedin.datahub.graphql.types.corpuser.mappers.CorpUserMapper;
1920
import com.linkedin.entity.EntityResponse;
2021
import com.linkedin.entity.client.EntityClient;
@@ -86,7 +87,10 @@ public CompletableFuture<AuthenticatedUser> get(DataFetchingEnvironment environm
8687
AuthorizationUtils.canManageOwnershipTypes(context));
8788
platformPrivileges.setManageGlobalAnnouncements(
8889
AuthorizationUtils.canManageGlobalAnnouncements(context));
89-
90+
platformPrivileges.setCreateBusinessAttributes(
91+
BusinessAttributeAuthorizationUtils.canCreateBusinessAttribute(context));
92+
platformPrivileges.setManageBusinessAttributes(
93+
BusinessAttributeAuthorizationUtils.canManageBusinessAttribute(context));
9094
// Construct and return authenticated user object.
9195
final AuthenticatedUser authUser = new AuthenticatedUser();
9296
authUser.setCorpUser(corpUser);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package com.linkedin.datahub.graphql.resolvers.businessattribute;
2+
3+
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;
4+
import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.buildMetadataChangeProposalWithUrn;
5+
import static com.linkedin.metadata.Constants.BUSINESS_ATTRIBUTE_ASPECT;
6+
7+
import com.linkedin.businessattribute.BusinessAttributeAssociation;
8+
import com.linkedin.businessattribute.BusinessAttributes;
9+
import com.linkedin.common.urn.BusinessAttributeUrn;
10+
import com.linkedin.common.urn.Urn;
11+
import com.linkedin.common.urn.UrnUtils;
12+
import com.linkedin.datahub.graphql.QueryContext;
13+
import com.linkedin.datahub.graphql.generated.AddBusinessAttributeInput;
14+
import com.linkedin.datahub.graphql.generated.ResourceRefInput;
15+
import com.linkedin.metadata.entity.EntityService;
16+
import com.linkedin.metadata.entity.EntityUtils;
17+
import com.linkedin.mxe.MetadataChangeProposal;
18+
import graphql.schema.DataFetcher;
19+
import graphql.schema.DataFetchingEnvironment;
20+
import java.net.URISyntaxException;
21+
import java.util.ArrayList;
22+
import java.util.List;
23+
import java.util.concurrent.CompletableFuture;
24+
import lombok.RequiredArgsConstructor;
25+
import lombok.extern.slf4j.Slf4j;
26+
27+
@Slf4j
28+
@RequiredArgsConstructor
29+
public class AddBusinessAttributeResolver implements DataFetcher<CompletableFuture<Boolean>> {
30+
private final EntityService entityService;
31+
32+
@Override
33+
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
34+
final QueryContext context = environment.getContext();
35+
final AddBusinessAttributeInput input =
36+
bindArgument(environment.getArgument("input"), AddBusinessAttributeInput.class);
37+
final Urn businessAttributeUrn = UrnUtils.getUrn(input.getBusinessAttributeUrn());
38+
final List<ResourceRefInput> resourceRefInputs = input.getResourceUrn();
39+
validateBusinessAttribute(businessAttributeUrn);
40+
return CompletableFuture.supplyAsync(
41+
() -> {
42+
try {
43+
addBusinessAttributeToResource(
44+
businessAttributeUrn,
45+
resourceRefInputs,
46+
UrnUtils.getUrn(context.getActorUrn()),
47+
entityService);
48+
return true;
49+
} catch (Exception e) {
50+
log.error(
51+
String.format(
52+
"Failed to add Business Attribute %s to resources %s",
53+
businessAttributeUrn, resourceRefInputs));
54+
throw new RuntimeException(
55+
String.format(
56+
"Failed to add Business Attribute %s to resources %s",
57+
businessAttributeUrn, resourceRefInputs),
58+
e);
59+
}
60+
});
61+
}
62+
63+
private void validateBusinessAttribute(Urn businessAttributeUrn) {
64+
if (!entityService.exists(businessAttributeUrn, true)) {
65+
throw new IllegalArgumentException(
66+
String.format("This urn does not exist: %s", businessAttributeUrn));
67+
}
68+
}
69+
70+
private void addBusinessAttributeToResource(
71+
Urn businessAttributeUrn,
72+
List<ResourceRefInput> resourceRefInputs,
73+
Urn actorUrn,
74+
EntityService entityService)
75+
throws URISyntaxException {
76+
List<MetadataChangeProposal> proposals = new ArrayList<>();
77+
for (ResourceRefInput resourceRefInput : resourceRefInputs) {
78+
proposals.add(
79+
buildAddBusinessAttributeToEntityProposal(
80+
businessAttributeUrn, resourceRefInput, entityService, actorUrn));
81+
}
82+
EntityUtils.ingestChangeProposals(proposals, entityService, actorUrn, false);
83+
}
84+
85+
private MetadataChangeProposal buildAddBusinessAttributeToEntityProposal(
86+
Urn businessAttributeUrn,
87+
ResourceRefInput resource,
88+
EntityService entityService,
89+
Urn actorUrn)
90+
throws URISyntaxException {
91+
BusinessAttributes businessAttributes =
92+
(BusinessAttributes)
93+
EntityUtils.getAspectFromEntity(
94+
resource.getResourceUrn(),
95+
BUSINESS_ATTRIBUTE_ASPECT,
96+
entityService,
97+
new BusinessAttributes());
98+
if (!businessAttributes.hasBusinessAttribute()) {
99+
businessAttributes.setBusinessAttribute(new BusinessAttributeAssociation());
100+
}
101+
BusinessAttributeAssociation businessAttributeAssociation =
102+
businessAttributes.getBusinessAttribute();
103+
businessAttributeAssociation.setBusinessAttributeUrn(
104+
BusinessAttributeUrn.createFromUrn(businessAttributeUrn));
105+
businessAttributes.setBusinessAttribute(businessAttributeAssociation);
106+
return buildMetadataChangeProposalWithUrn(
107+
UrnUtils.getUrn(resource.getResourceUrn()), BUSINESS_ATTRIBUTE_ASPECT, businessAttributes);
108+
}
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.linkedin.datahub.graphql.resolvers.businessattribute;
2+
3+
import com.datahub.authorization.AuthUtil;
4+
import com.datahub.authorization.ConjunctivePrivilegeGroup;
5+
import com.datahub.authorization.DisjunctivePrivilegeGroup;
6+
import com.google.common.collect.ImmutableList;
7+
import com.linkedin.datahub.graphql.QueryContext;
8+
import com.linkedin.metadata.authorization.PoliciesConfig;
9+
import javax.annotation.Nonnull;
10+
11+
public class BusinessAttributeAuthorizationUtils {
12+
private BusinessAttributeAuthorizationUtils() {}
13+
14+
public static boolean canCreateBusinessAttribute(@Nonnull QueryContext context) {
15+
final DisjunctivePrivilegeGroup orPrivilegeGroups =
16+
new DisjunctivePrivilegeGroup(
17+
ImmutableList.of(
18+
new ConjunctivePrivilegeGroup(
19+
ImmutableList.of(PoliciesConfig.CREATE_BUSINESS_ATTRIBUTE_PRIVILEGE.getType())),
20+
new ConjunctivePrivilegeGroup(
21+
ImmutableList.of(
22+
PoliciesConfig.MANAGE_BUSINESS_ATTRIBUTE_PRIVILEGE.getType()))));
23+
return AuthUtil.isAuthorized(
24+
context.getAuthorizer(), context.getActorUrn(), orPrivilegeGroups, null);
25+
}
26+
27+
public static boolean canManageBusinessAttribute(@Nonnull QueryContext context) {
28+
final DisjunctivePrivilegeGroup orPrivilegeGroups =
29+
new DisjunctivePrivilegeGroup(
30+
ImmutableList.of(
31+
new ConjunctivePrivilegeGroup(
32+
ImmutableList.of(
33+
PoliciesConfig.MANAGE_BUSINESS_ATTRIBUTE_PRIVILEGE.getType()))));
34+
return AuthUtil.isAuthorized(
35+
context.getAuthorizer(), context.getActorUrn(), orPrivilegeGroups, null);
36+
}
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.linkedin.datahub.graphql.resolvers.businessattribute;
2+
3+
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.bindArgument;
4+
import static com.linkedin.datahub.graphql.resolvers.mutate.MutationUtils.buildMetadataChangeProposalWithKey;
5+
import static com.linkedin.metadata.Constants.BUSINESS_ATTRIBUTE_ENTITY_NAME;
6+
import static com.linkedin.metadata.Constants.BUSINESS_ATTRIBUTE_INFO_ASPECT_NAME;
7+
8+
import com.linkedin.businessattribute.BusinessAttributeInfo;
9+
import com.linkedin.businessattribute.BusinessAttributeKey;
10+
import com.linkedin.common.AuditStamp;
11+
import com.linkedin.common.urn.Urn;
12+
import com.linkedin.common.urn.UrnUtils;
13+
import com.linkedin.data.template.SetMode;
14+
import com.linkedin.datahub.graphql.QueryContext;
15+
import com.linkedin.datahub.graphql.exception.AuthorizationException;
16+
import com.linkedin.datahub.graphql.exception.DataHubGraphQLErrorCode;
17+
import com.linkedin.datahub.graphql.exception.DataHubGraphQLException;
18+
import com.linkedin.datahub.graphql.generated.BusinessAttribute;
19+
import com.linkedin.datahub.graphql.generated.CreateBusinessAttributeInput;
20+
import com.linkedin.datahub.graphql.generated.OwnerEntityType;
21+
import com.linkedin.datahub.graphql.resolvers.mutate.util.BusinessAttributeUtils;
22+
import com.linkedin.datahub.graphql.resolvers.mutate.util.OwnerUtils;
23+
import com.linkedin.datahub.graphql.types.businessattribute.mappers.BusinessAttributeMapper;
24+
import com.linkedin.entity.client.EntityClient;
25+
import com.linkedin.metadata.entity.EntityService;
26+
import com.linkedin.metadata.service.BusinessAttributeService;
27+
import com.linkedin.metadata.utils.EntityKeyUtils;
28+
import com.linkedin.mxe.MetadataChangeProposal;
29+
import graphql.schema.DataFetcher;
30+
import graphql.schema.DataFetchingEnvironment;
31+
import java.util.UUID;
32+
import java.util.concurrent.CompletableFuture;
33+
import lombok.RequiredArgsConstructor;
34+
import lombok.extern.slf4j.Slf4j;
35+
36+
@Slf4j
37+
@RequiredArgsConstructor
38+
public class CreateBusinessAttributeResolver
39+
implements DataFetcher<CompletableFuture<BusinessAttribute>> {
40+
private final EntityClient _entityClient;
41+
private final EntityService _entityService;
42+
private final BusinessAttributeService businessAttributeService;
43+
44+
@Override
45+
public CompletableFuture<BusinessAttribute> get(DataFetchingEnvironment environment)
46+
throws Exception {
47+
final QueryContext context = environment.getContext();
48+
CreateBusinessAttributeInput input =
49+
bindArgument(environment.getArgument("input"), CreateBusinessAttributeInput.class);
50+
if (!BusinessAttributeAuthorizationUtils.canCreateBusinessAttribute(context)) {
51+
throw new AuthorizationException(
52+
"Unauthorized to perform this action. Please contact your DataHub administrator.");
53+
}
54+
return CompletableFuture.supplyAsync(
55+
() -> {
56+
try {
57+
final BusinessAttributeKey businessAttributeKey = new BusinessAttributeKey();
58+
String id = input.getId() != null ? input.getId() : UUID.randomUUID().toString();
59+
businessAttributeKey.setId(id);
60+
61+
if (_entityClient.exists(
62+
EntityKeyUtils.convertEntityKeyToUrn(
63+
businessAttributeKey, BUSINESS_ATTRIBUTE_ENTITY_NAME),
64+
context.getAuthentication())) {
65+
throw new IllegalArgumentException("This Business Attribute already exists!");
66+
}
67+
68+
if (BusinessAttributeUtils.hasNameConflict(input.getName(), context, _entityClient)) {
69+
throw new DataHubGraphQLException(
70+
String.format(
71+
"\"%s\" already exists as Business Attribute. Please pick a unique name.",
72+
input.getName()),
73+
DataHubGraphQLErrorCode.CONFLICT);
74+
}
75+
76+
// Create the MCP
77+
final MetadataChangeProposal changeProposal =
78+
buildMetadataChangeProposalWithKey(
79+
businessAttributeKey,
80+
BUSINESS_ATTRIBUTE_ENTITY_NAME,
81+
BUSINESS_ATTRIBUTE_INFO_ASPECT_NAME,
82+
mapBusinessAttributeInfo(input, context));
83+
84+
// Ingest the MCP
85+
Urn businessAttributeUrn =
86+
UrnUtils.getUrn(
87+
_entityClient.ingestProposal(changeProposal, context.getAuthentication()));
88+
OwnerUtils.addCreatorAsOwner(
89+
context,
90+
businessAttributeUrn.toString(),
91+
OwnerEntityType.CORP_USER,
92+
_entityService);
93+
return BusinessAttributeMapper.map(
94+
context,
95+
businessAttributeService.getBusinessAttributeEntityResponse(
96+
businessAttributeUrn, context.getAuthentication()));
97+
98+
} catch (DataHubGraphQLException e) {
99+
throw e;
100+
} catch (Exception e) {
101+
log.error(
102+
"Failed to create Business Attribute with name: {}: {}",
103+
input.getName(),
104+
e.getMessage());
105+
throw new RuntimeException(
106+
String.format("Failed to create Business Attribute with name: %s", input.getName()),
107+
e);
108+
}
109+
});
110+
}
111+
112+
private BusinessAttributeInfo mapBusinessAttributeInfo(
113+
CreateBusinessAttributeInput input, QueryContext context) {
114+
final BusinessAttributeInfo info = new BusinessAttributeInfo();
115+
info.setFieldPath(input.getName(), SetMode.DISALLOW_NULL);
116+
info.setName(input.getName(), SetMode.DISALLOW_NULL);
117+
info.setDescription(input.getDescription(), SetMode.IGNORE_NULL);
118+
info.setType(
119+
BusinessAttributeUtils.mapSchemaFieldDataType(input.getType()), SetMode.IGNORE_NULL);
120+
info.setCreated(
121+
new AuditStamp()
122+
.setActor(UrnUtils.getUrn(context.getActorUrn()))
123+
.setTime(System.currentTimeMillis()));
124+
info.setLastModified(
125+
new AuditStamp()
126+
.setActor(UrnUtils.getUrn(context.getActorUrn()))
127+
.setTime(System.currentTimeMillis()));
128+
return info;
129+
}
130+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.linkedin.datahub.graphql.resolvers.businessattribute;
2+
3+
import com.linkedin.common.urn.Urn;
4+
import com.linkedin.common.urn.UrnUtils;
5+
import com.linkedin.datahub.graphql.QueryContext;
6+
import com.linkedin.datahub.graphql.exception.AuthorizationException;
7+
import com.linkedin.entity.client.EntityClient;
8+
import graphql.schema.DataFetcher;
9+
import graphql.schema.DataFetchingEnvironment;
10+
import java.util.concurrent.CompletableFuture;
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.extern.slf4j.Slf4j;
13+
14+
/** Resolver responsible for hard deleting a particular Business Attribute */
15+
@Slf4j
16+
@RequiredArgsConstructor
17+
public class DeleteBusinessAttributeResolver implements DataFetcher<CompletableFuture<Boolean>> {
18+
private final EntityClient _entityClient;
19+
20+
@Override
21+
public CompletableFuture<Boolean> get(DataFetchingEnvironment environment) throws Exception {
22+
final QueryContext context = environment.getContext();
23+
final Urn businessAttributeUrn = UrnUtils.getUrn(environment.getArgument("urn"));
24+
if (!BusinessAttributeAuthorizationUtils.canManageBusinessAttribute(context)) {
25+
throw new AuthorizationException(
26+
"Unauthorized to perform this action. Please contact your DataHub administrator.");
27+
}
28+
if (!_entityClient.exists(businessAttributeUrn, context.getAuthentication())) {
29+
throw new RuntimeException(
30+
String.format("This urn does not exist: %s", businessAttributeUrn));
31+
}
32+
return CompletableFuture.supplyAsync(
33+
() -> {
34+
try {
35+
_entityClient.deleteEntity(businessAttributeUrn, context.getAuthentication());
36+
CompletableFuture.runAsync(
37+
() -> {
38+
try {
39+
_entityClient.deleteEntityReferences(
40+
businessAttributeUrn, context.getAuthentication());
41+
} catch (Exception e) {
42+
log.error(
43+
String.format(
44+
"Exception while attempting to clear all entity references for Business Attribute with urn %s",
45+
businessAttributeUrn),
46+
e);
47+
}
48+
});
49+
return true;
50+
} catch (Exception e) {
51+
throw new RuntimeException(
52+
String.format(
53+
"Failed to delete Business Attribute with urn %s", businessAttributeUrn),
54+
e);
55+
}
56+
});
57+
}
58+
}

0 commit comments

Comments
 (0)