-
Notifications
You must be signed in to change notification settings - Fork 3k
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(GraphQL): Support for Deleting Domains, Tags via GraphQL API #5272
Changes from all commits
d9db3a6
730cd27
45c24ac
1fb0123
5cdc322
f0e5d10
fe738d9
7c91470
2f86122
827b7fc
7c9a515
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
package com.linkedin.datahub.graphql.resolvers.domain; | ||
|
||
import com.linkedin.common.urn.Urn; | ||
import com.linkedin.datahub.graphql.QueryContext; | ||
import com.linkedin.datahub.graphql.authorization.AuthorizationUtils; | ||
import com.linkedin.datahub.graphql.exception.AuthorizationException; | ||
import com.linkedin.entity.client.EntityClient; | ||
import com.linkedin.r2.RemoteInvocationException; | ||
import graphql.schema.DataFetcher; | ||
import graphql.schema.DataFetchingEnvironment; | ||
import java.util.concurrent.CompletableFuture; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
|
||
/** | ||
* Resolver responsible for hard deleting a particular DataHub Corp Group | ||
*/ | ||
@Slf4j | ||
public class DeleteDomainResolver implements DataFetcher<CompletableFuture<Boolean>> { | ||
|
||
private final EntityClient _entityClient; | ||
|
||
public DeleteDomainResolver(final EntityClient entityClient) { | ||
_entityClient = entityClient; | ||
} | ||
|
||
@Override | ||
public CompletableFuture<Boolean> get(final DataFetchingEnvironment environment) throws Exception { | ||
final QueryContext context = environment.getContext(); | ||
final String domainUrn = environment.getArgument("urn"); | ||
final Urn urn = Urn.createFromString(domainUrn); | ||
return CompletableFuture.supplyAsync(() -> { | ||
|
||
if (AuthorizationUtils.canManageDomains(context) || AuthorizationUtils.canDeleteEntity(urn, context)) { | ||
try { | ||
_entityClient.deleteEntity(urn, context.getAuthentication()); | ||
|
||
// Asynchronously Delete all references to the entity (to return quickly) | ||
CompletableFuture.runAsync(() -> { | ||
try { | ||
_entityClient.deleteEntityReferences(urn, context.getAuthentication()); | ||
} catch (RemoteInvocationException e) { | ||
log.error(String.format("Caught exception while attempting to clear all entity references for Domain with urn %s", urn), e); | ||
} | ||
}); | ||
|
||
return true; | ||
} catch (Exception e) { | ||
throw new RuntimeException(String.format("Failed to perform delete against domain with urn %s", domainUrn), e); | ||
} | ||
} | ||
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator."); | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,7 +45,7 @@ public CompletableFuture<ListDomainsResult> get(final DataFetchingEnvironment en | |
|
||
return CompletableFuture.supplyAsync(() -> { | ||
|
||
if (AuthorizationUtils.canManageDomains(context)) { | ||
if (AuthorizationUtils.canCreateDomains(context)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume this change will be reflected in the frontend in the next PR (or one of the followups)? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes! Actually I should add one more thing to this PR to setup that change. brb |
||
final ListDomainsInput input = bindArgument(environment.getArgument("input"), ListDomainsInput.class); | ||
final Integer start = input.getStart() == null ? DEFAULT_START : input.getStart(); | ||
final Integer count = input.getCount() == null ? DEFAULT_COUNT : input.getCount(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
package com.linkedin.datahub.graphql.resolvers.tag; | ||
|
||
import com.linkedin.data.template.SetMode; | ||
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.CreateTagInput; | ||
import com.linkedin.events.metadata.ChangeType; | ||
import com.linkedin.metadata.Constants; | ||
import com.linkedin.metadata.entity.EntityService; | ||
import com.linkedin.metadata.key.TagKey; | ||
import com.linkedin.metadata.utils.EntityKeyUtils; | ||
import com.linkedin.metadata.utils.GenericRecordUtils; | ||
import com.linkedin.mxe.MetadataChangeProposal; | ||
import com.linkedin.tag.TagProperties; | ||
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; | ||
|
||
import static com.linkedin.datahub.graphql.authorization.AuthorizationUtils.*; | ||
import static com.linkedin.datahub.graphql.resolvers.ResolverUtils.*; | ||
|
||
/** | ||
* Resolver used for creating a new Tag on DataHub. Requires the CREATE_TAG or MANAGE_TAGS privilege. | ||
*/ | ||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class CreateTagResolver implements DataFetcher<CompletableFuture<String>> { | ||
|
||
private final EntityService _entityService; | ||
|
||
@Override | ||
public CompletableFuture<String> get(DataFetchingEnvironment environment) throws Exception { | ||
|
||
final QueryContext context = environment.getContext(); | ||
final CreateTagInput input = bindArgument(environment.getArgument("input"), CreateTagInput.class); | ||
|
||
return CompletableFuture.supplyAsync(() -> { | ||
|
||
if (!AuthorizationUtils.canCreateTags(context)) { | ||
throw new AuthorizationException("Unauthorized to perform this action. Please contact your DataHub administrator."); | ||
} | ||
|
||
try { | ||
// Create the Tag Key | ||
final TagKey key = new TagKey(); | ||
|
||
// Take user provided id OR generate a random UUID for the Tag. | ||
final String id = input.getId() != null ? input.getId() : UUID.randomUUID().toString(); | ||
key.setName(id); | ||
|
||
if (_entityService.exists(EntityKeyUtils.convertEntityKeyToUrn(key, Constants.TAG_ENTITY_NAME))) { | ||
throw new IllegalArgumentException("This Tag already exists!"); | ||
} | ||
|
||
// Create the MCP | ||
final MetadataChangeProposal proposal = new MetadataChangeProposal(); | ||
proposal.setEntityKeyAspect(GenericRecordUtils.serializeAspect(key)); | ||
proposal.setEntityType(Constants.TAG_ENTITY_NAME); | ||
proposal.setAspectName(Constants.TAG_PROPERTIES_ASPECT_NAME); | ||
proposal.setAspect(GenericRecordUtils.serializeAspect(mapTagProperties(input))); | ||
proposal.setChangeType(ChangeType.UPSERT); | ||
return _entityService.ingestProposal(proposal, createAuditStamp(context)).getUrn().toString(); | ||
} catch (Exception e) { | ||
log.error("Failed to create Domain with id: {}, name: {}: {}", input.getId(), input.getName(), e.getMessage()); | ||
throw new RuntimeException(String.format("Failed to create Domain with id: %s, name: %s", input.getId(), input.getName()), e); | ||
} | ||
}); | ||
} | ||
|
||
private TagProperties mapTagProperties(final CreateTagInput input) { | ||
final TagProperties result = new TagProperties(); | ||
result.setName(input.getName()); | ||
result.setDescription(input.getDescription(), SetMode.IGNORE_NULL); | ||
return result; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so the user has to have this
DELETE_ENTITY_PRIVILEGE
specifically for the entity type that you pass in right? aka it's possible to have this privilege for Tags but not DomainsThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes correct!